test_cli.py 19.4 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
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
    # def test_clean(self, published):
    #     upload_id = published.upload_id
73

74
75
76
77
    #     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
78

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

82
83
84
85
    #     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
86

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

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

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

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

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

        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

127

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


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

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

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

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

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

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

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

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

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

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

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

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

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

        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

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

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

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

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

    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
220

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

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

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

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

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

        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

273
274
275
276
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
    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)

306
307
308
309
    def test_reset(self, non_empty_processed):
        upload_id = non_empty_processed.upload_id

        result = click.testing.CliRunner().invoke(
310
            cli, ['admin', 'uploads', 'reset', '--with-calcs', upload_id], catch_exceptions=False)
311
312
313
314
315
316
317
318
319

        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

320

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

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

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

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

        assert result.exit_code == 0

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

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

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

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

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

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

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

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

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

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

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

        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

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

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

        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
434

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

        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

464
465
466
    def test_statistics(self, client, proc_infra, admin_user_bravado_client):

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

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