test_cli.py 13.7 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 processing as proc, files
27
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
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
def invoke_cli(*args, **kwargs):
    return click.testing.CliRunner().invoke(*args, obj=POPO(), **kwargs)


43
44
45
46
47
@pytest.mark.usefixtures('reset_config', 'nomad_logging')
class TestCli:
    def test_help(self, example_mainfile):

        start = time.time()
48
        result = invoke_cli(
49
50
51
52
53
            cli, ['--help'], catch_exceptions=False)
        assert result.exit_code == 0
        assert time.time() - start < 1


54
@pytest.mark.usefixtures('reset_config', 'nomad_logging')
Markus Scheidgen's avatar
Markus Scheidgen committed
55
56
57
class TestParse:
    def test_parser(self, example_mainfile):
        _, mainfile_path = example_mainfile
58
        result = invoke_cli(
Markus Scheidgen's avatar
Markus Scheidgen committed
59
60
61
62
            cli, ['parse', mainfile_path], catch_exceptions=False)
        assert result.exit_code == 0


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

    def test_reset_not_sure(self):
71
        result = invoke_cli(
72
            cli, ['admin', 'reset'], catch_exceptions=False)
73
74
        assert result.exit_code == 1

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

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

89
    #     result = invoke_cli(
90
    #         cli, ['admin', 'clean', '--force', '--skip-es'], catch_exceptions=False)
91

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

100
    @pytest.mark.parametrize('publish_time,dry,lifted', [
101
102
103
        (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)])
104
    def test_lift_embargo(self, published, publish_time, dry, lifted):
105
        upload_id = published.upload_id
106
        published.publish_time = publish_time
107
108
109
110
111
112
113
        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

114
        result = invoke_cli(
115
            cli, ['admin', 'lift-embargo'] + (['--dry'] if dry else []),
116
            catch_exceptions=False)
117
118

        assert result.exit_code == 0
David Sikter's avatar
David Sikter committed
119
        published.block_until_complete()
120
121
122
        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
    def test_delete_entry(self, published):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()

130
        result = invoke_cli(
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
    @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)
156
        result = invoke_cli(
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
    def test_query_mongo(self, published):
        upload_id = published.upload_id

        query = dict(upload_id=upload_id)
166
        result = invoke_cli(
Markus Scheidgen's avatar
Markus Scheidgen committed
167
            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
        upload_id = published.upload_id

176
        result = invoke_cli(
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
    def test_ls_query(self, published):
        upload_id = published.upload_id

185
        result = invoke_cli(
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
        upload_id = published.upload_id

193
        result = invoke_cli(
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 = invoke_cli(
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

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

220
        result = invoke_cli(
221
222
223
224
225
226
227
228
229
            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
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
        assert calc.metadata['nomad_version'] != 'test_version'

237
        result = invoke_cli(
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
    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()

252
        result = invoke_cli(
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 = invoke_cli(
275
            cli, ['admin', 'uploads', 'chown', other_test_user.username, upload_id], catch_exceptions=False)
276
277
278
279
280

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

        upload = Upload.objects(upload_id=upload_id).first()
281
        upload.block_until_complete()
282
283
        calc.reload()

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

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

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

        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)
305
        result = invoke_cli(cli, args, catch_exceptions=False)
306
307
308
309
310
311

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

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

321

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

325
326
    def test_upload(self, non_empty_example_upload, admin_user, proc_infra, client_with_api_v1):
        result = invoke_cli(
Markus Scheidgen's avatar
Markus Scheidgen committed
327
            cli,
328
329
330
331
            [
                'client', '-u', admin_user.username, '--token-via-api',
                'upload', '--name', 'test_upload', '--local-path',
                non_empty_example_upload],
332
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
333

334
        assert result.exit_code == 0, result.output
Markus Scheidgen's avatar
Markus Scheidgen committed
335
336
337
        assert '1/0/1' in result.output
        assert proc.Upload.objects(name='test_upload').first() is not None

338
339
    def test_local(self, published_wo_user_metadata, client_with_api_v1):
        result = invoke_cli(
Markus Scheidgen's avatar
Markus Scheidgen committed
340
            cli,
341
342
            ['client', 'local', published_wo_user_metadata.calcs[0].calc_id],
            catch_exceptions=True)
Markus Scheidgen's avatar
Markus Scheidgen committed
343

344
        assert result.exit_code == 0, result.output
Markus Scheidgen's avatar
Markus Scheidgen committed
345

346
    @pytest.mark.skip('Disabled. Tested code is temporaely commented.')
347
348
    def test_statistics(self, client, proc_infra, admin_user_bravado_client):

349
        result = invoke_cli(
350
            cli, ['client', 'statistics-table'], catch_exceptions=True)
351
352
353

        assert result.exit_code == 0, result.output
        assert 'Calculations, e.g. total energies' in result.output
354
        assert 'Geometries' in result.output
355
356
357
358
359
        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