test_cli.py 19.6 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, processing as proc, files
27
from nomad.cli import cli
28
from nomad.cli.cli import POPO
29
from nomad.processing import Upload, Calc
30
from nomad.processing.base import SUCCESS
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
    # def test_clean(self, published):
    #     upload_id = published.upload_id
74

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
    #     assert search.SearchRequest().search_parameter('upload_id', upload_id).execute()['total'] > 0
79

80
81
    #     result = click.testing.CliRunner().invoke(
    #         cli, ['admin', 'clean', '--force', '--skip-es'], catch_exceptions=False)
82

83
84
85
86
    #     assert result.exit_code == 0
    #     assert not published.upload_files.exists()
    #     assert Calc.objects(upload_id=upload_id).first() is None
    #     assert search.SearchRequest().search_parameter('upload_id', upload_id).execute()['total'] > 0
87

88
89
90
91
92
93
94
95
96
97
98
99
100
    @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
101
        with pytest.raises(Exception):
102
103
            with files.UploadFiles.get(upload_id=upload_id).read_archive(calc_id=calc.calc_id):
                pass
104
105
106

        result = click.testing.CliRunner().invoke(
            cli, ['admin', 'lift-embargo'] + (['--dry'] if dry else []),
107
            catch_exceptions=False)
108
109
110
111
112

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

116
117
118
119
120
    def test_delete_entry(self, published):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()

        result = click.testing.CliRunner().invoke(
121
            cli, ['admin', 'entries', 'rm', calc.calc_id], catch_exceptions=False)
122
123
124
125
126
127

        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

128

129
130
131
132
133
def transform_for_index_test(calc):
    calc.comment = 'specific'
    return calc


134
@pytest.mark.usefixtures('reset_config', 'no_warn')
135
136
class TestAdminUploads:

137
138
139
140
141
142
143
144
145
146
    @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(
147
            cli, ['admin', 'uploads'] + codes_args + ['ls'], catch_exceptions=False)
148
149
150
151

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

Markus Scheidgen's avatar
Markus Scheidgen committed
152
153
154
155
156
157
    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)],
158
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
159
160
161
162

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

163
    def test_ls(self, published):
164
165
166
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
167
            cli, ['admin', 'uploads', 'ls', upload_id], catch_exceptions=False)
168
169
170
171

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

172
173
174
175
    def test_ls_query(self, published):
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
176
            cli, ['admin', 'uploads', 'ls', '{"match":{"upload_id":"%s"}}' % upload_id], catch_exceptions=False)
177
178
179
180

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

181
    def test_rm(self, published):
182
183
184
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
185
            cli, ['admin', 'uploads', 'rm', upload_id], catch_exceptions=False)
186
187
188
189
190
191

        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

192
193
194
195
196
197
    def test_index(self, published):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
        calc.metadata['comment'] = 'specific'
        calc.save()

198
        assert search.SearchRequest().search_parameters(comment='specific').execute()['total'] == 0
199
200

        result = click.testing.CliRunner().invoke(
201
            cli, ['admin', 'uploads', 'index', upload_id], catch_exceptions=False)
202
203
204
        assert result.exit_code == 0
        assert 'index' in result.stdout

205
        assert search.SearchRequest().search_parameters(comment='specific').execute()['total'] == 1
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

    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
221

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

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

236
237
238
239
240
241
242
243
    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(
244
            cli, ['admin', 'uploads', 're-pack', '--parallel', '2', upload_id], catch_exceptions=False)
245
246
247
248
249
250
251
252
253

        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):
254
255
            with upload_files.read_archive(calc.calc_id) as archive:
                assert calc.calc_id in archive
256

257
258
259
        published.reload()
        assert published.tasks_status == SUCCESS

260
261
262
263
264
265
    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(
266
            cli, ['admin', 'uploads', 'chown', test_user.username, upload_id], catch_exceptions=False)
267
268
269
270
271
272
273
274
275
276

        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

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
    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

            for calc in search.search(owner=None, query=dict(upload_id=upload_id)).data:
                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)

310
311
312
313
    def test_reset(self, non_empty_processed):
        upload_id = non_empty_processed.upload_id

        result = click.testing.CliRunner().invoke(
314
            cli, ['admin', 'uploads', 'reset', '--with-calcs', upload_id], catch_exceptions=False)
315
316
317
318
319
320
321
322
323

        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

324

325
@pytest.mark.usefixtures('reset_config')
326
327
class TestClient:

Markus Scheidgen's avatar
Markus Scheidgen committed
328
329
330
331
    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],
332
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
333
334
335
336
337
338
339
340

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

        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)],
349
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
350
351
352

        assert result.exit_code == 0

353
354
355
    def test_mirror_dry(self, published, admin_user_bravado_client, monkeypatch):
        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

356
        result = click.testing.CliRunner().invoke(
357
            cli, ['client', 'mirror', '--dry'], catch_exceptions=False)
358
359
360
361
362

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

363
364
    @pytest.mark.parametrize('move, link', [(True, False), (False, True), (False, False)])
    def test_mirror(self, published, admin_user_bravado_client, monkeypatch, move, link):
365
366
367
        ref_search_results = search.flat(
            search.SearchRequest().search_parameters(
                upload_id=published.upload_id).execute_paginated()['results'][0])
368
369
370

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

371
372
        if move:
            result = click.testing.CliRunner().invoke(
373
                cli, ['client', 'mirror', '--move'], catch_exceptions=False)
374
375
        elif link:
            result = click.testing.CliRunner().invoke(
376
                cli, ['client', 'mirror', '--link'], catch_exceptions=False)
377
378
        else:
            result = click.testing.CliRunner().invoke(
379
                cli, ['client', 'mirror', '--source-mapping', '.volumes/test_fs:.volumes/test_fs'], catch_exceptions=False)
380
381

        assert result.exit_code == 0
382
383
        assert published.upload_id in result.output
        assert published.upload_files.os_path in result.output
384
385
        assert proc.Upload.objects(upload_id=published.upload_id).count() == 1
        assert proc.Calc.objects(upload_id=published.upload_id).count() == 1
386
        new_search = search.SearchRequest().search_parameters(upload_id=published.upload_id).execute_paginated()
387
388
389
        calcs_in_search = new_search['pagination']['total']
        assert calcs_in_search == 1

390
        new_search_results = search.flat(new_search['results'][0])
391
        for key in new_search_results.keys():
392
            if key not in ['upload_time', 'last_processing', 'dft.labels.label', 'owners', 'authors', 'uploader', 'coauthors', 'shared_with']:
Markus Scheidgen's avatar
Markus Scheidgen committed
393
394
                # There is a sub second change due to date conversions (?).
                # Labels have arbitrary order.
395
396
397
                assert json.dumps(new_search_results[key]) == json.dumps(ref_search_results[key])

        published.upload_files.exists
398
399
400
        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):
401
402
403
        ref_search_results = search.flat(
            search.SearchRequest().search_parameters(
                upload_id=non_empty_processed.upload_id).execute_paginated()['results'][0])
404
405
406
407

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

        result = click.testing.CliRunner().invoke(
408
            cli, ['client', 'mirror', '--staging', '--link'], catch_exceptions=False)
409
410
411
412
413
414
415
416
417
418

        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

419
        new_search_results = search.flat(new_search['results'][0])
420
421
422
423
424
425
        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
426
427
428
429
430

    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(
431
            cli, ['client', 'mirror', '--files-only'], catch_exceptions=False)
432
433
434
435
436
437

        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
438

439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
    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(
460
            cli, ['client', 'mirror'], catch_exceptions=False)
461
462
463
464
465
466
467

        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

468
469
470
    def test_statistics(self, client, proc_infra, admin_user_bravado_client):

        result = click.testing.CliRunner().invoke(
471
            cli, ['client', 'statistics-table'], catch_exceptions=True)
472
473
474

        assert result.exit_code == 0, result.output
        assert 'Calculations, e.g. total energies' in result.output
475
        assert 'Geometries' in result.output
476
477
478
479
480
        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