Project

General

Profile

Download (16.6 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / test / haketilo_test / unit / test_install.py @ fd9f2fc4

1
# SPDX-License-Identifier: CC0-1.0
2

    
3
"""
4
Haketilo unit tests - item installation dialog
5
"""
6

    
7
# This file is part of Haketilo
8
#
9
# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the CC0 1.0 Universal License as published by
13
# the Creative Commons Corporation.
14
#
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
# CC0 1.0 Universal License for more details.
19

    
20
import pytest
21
import json
22
from selenium.webdriver.support.ui import WebDriverWait
23

    
24
from ..extension_crafting import ExtraHTML
25
from ..script_loader import load_script
26
from .utils import *
27

    
28
def setup_view(driver, execute_in_page):
29
    mock_cacher(execute_in_page)
30

    
31
    execute_in_page(load_script('html/install.js'))
32
    container_ids, containers_objects = execute_in_page(
33
        '''
34
        const cb_calls = [];
35
        const install_view = new InstallView(0,
36
                                             () => cb_calls.push("show"),
37
                                             () => cb_calls.push("hide"));
38
        document.body.append(install_view.main_div);
39
        const ets = () => install_view.item_entries;
40
        const shw = slice => [cb_calls.slice(slice || 0), install_view.shown];
41
        returnval([container_ids, container_ids.map(cid => install_view[cid])]);
42
        ''')
43

    
44
    containers = dict(zip(container_ids, containers_objects))
45

    
46
    def assert_container_displayed(container_id):
47
        for cid, cobj in zip(container_ids, containers_objects):
48
            assert (cid == container_id) == cobj.is_displayed()
49

    
50
    return containers, assert_container_displayed
51

    
52
install_ext_data = {
53
    'background_script': broker_js,
54
    'extra_html': ExtraHTML('html/install.html', {}),
55
    'navigate_to': 'html/install.html'
56
}
57

    
58
@pytest.mark.ext_data(install_ext_data)
59
@pytest.mark.usefixtures('webextension')
60
@pytest.mark.parametrize('complex_variant', [False, True])
61
def test_install_normal_usage(driver, execute_in_page, complex_variant):
62
    """
63
    Test of the normal package installation procedure with one mapping and,
64
    depending on parameter, one or many resources.
65
    """
66
    containers, assert_container_displayed = setup_view(driver, execute_in_page)
67

    
68
    assert execute_in_page('returnval(shw());') == [[], False]
69

    
70
    if complex_variant:
71
        # The resource/mapping others depend on.
72
        root_id = 'abcd-defg-ghij'
73
        root_resource_id = f'resource_{root_id}'
74
        root_mapping_id = f'mapping_{root_id}'
75
        # Those ids are used to check the alphabetical ordering.
76
        resource_ids = [f'resource_{letters}' for letters in (
77
            'a', 'abcd', root_id, 'b', 'c',
78
            'd', 'defg', 'e', 'f',
79
            'g', 'ghij', 'h', 'i', 'j'
80
        )]
81
        files_count = 9
82
    else:
83
        root_resource_id = f'resource_a'
84
        root_mapping_id = f'mapping_a'
85
        resource_ids = [root_resource_id]
86
        files_count = 0
87

    
88
    # Preview the installation of a resource, show resource's details, close
89
    # the details and cancel installation.
90
    execute_in_page('returnval(install_view.show(...arguments));',
91
                    'https://hydril.la/', 'resource', root_resource_id)
92

    
93
    assert execute_in_page('returnval(shw());') == [['show'], True]
94
    assert f'{root_resource_id}-2021.11.11-1'\
95
        in containers['install_preview'].text
96
    assert_container_displayed('install_preview')
97

    
98
    entries = execute_in_page('returnval(ets().map(e => e.main_li.innerText));')
99
    assert len(entries) == len(resource_ids)
100
    # Verify alphabetical ordering.
101
    assert all([id in text for id, text in zip(resource_ids, entries)])
102

    
103
    assert not execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
104
    execute_in_page('returnval(ets()[0].details_but);').click()
105
    assert 'resource_a' in containers['resource_preview_container'].text
106
    assert_container_displayed('resource_preview_container')
107

    
108
    execute_in_page('returnval(install_view.resource_back_but);').click()
109
    assert_container_displayed('install_preview')
110

    
111
    assert execute_in_page('returnval(shw());') == [['show'], True]
112
    execute_in_page('returnval(install_view.cancel_but);').click()
113
    assert execute_in_page('returnval(shw());') == [['show', 'hide'], False]
114

    
115
    # Preview the installation of a mapping and a resource, show mapping's
116
    # details, close the details and commit the installation.
117
    execute_in_page('returnval(install_view.show(...arguments));',
118
                    'https://hydril.la/', 'mapping',
119
                    root_mapping_id, [2022, 5, 10])
120

    
121
    assert execute_in_page('returnval(shw(2));') == [['show'], True]
122
    assert_container_displayed('install_preview')
123

    
124
    entries = execute_in_page('returnval(ets().map(e => e.main_li.innerText));')
125
    assert len(entries) == len(resource_ids) + 1
126
    assert f'{root_mapping_id}-2022.5.10' in entries[0]
127
    # Verify alphabetical ordering.
128
    assert all([id in text for id, text in zip(resource_ids, entries[1:])])
129

    
130
    assert not execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
131
    execute_in_page('returnval(ets()[0].details_but);').click()
132
    assert root_mapping_id in containers['mapping_preview_container'].text
133
    assert_container_displayed('mapping_preview_container')
134

    
135
    execute_in_page('returnval(install_view.mapping_back_but);').click()
136
    assert_container_displayed('install_preview')
137

    
138
    execute_in_page('returnval(install_view.install_but);').click()
139
    installed = lambda d: 'ly installed!' in containers['dialog_container'].text
140
    WebDriverWait(driver, 10).until(installed)
141

    
142
    assert execute_in_page('returnval(shw(2));') == [['show'], True]
143
    execute_in_page('returnval(install_view.dialog_ctx.ok_but);').click()
144
    assert execute_in_page('returnval(shw(2));') == [['show', 'hide'], False]
145

    
146
    # Verify the install
147
    db_contents = get_db_contents(execute_in_page)
148
    for item_type, ids in \
149
        [('mapping', {root_mapping_id}), ('resource', set(resource_ids))]:
150
        assert set([it['identifier'] for it in db_contents[item_type]]) == ids
151

    
152
    assert all([len(db_contents[store]) == files_count
153
                for store in ('file', 'file_uses')])
154

    
155
    # Update the installed mapping to a newer version.
156
    execute_in_page('returnval(install_view.show(...arguments));',
157
                    'https://hydril.la/', 'mapping', root_mapping_id)
158
    assert execute_in_page('returnval(shw(4));') == [['show'], True]
159
    # resources are already in the newest versions, hence they should not appear
160
    # in the install preview list.
161
    assert execute_in_page('returnval(ets().length);') == 1
162
    # Mapping's version update information should be displayed.
163
    assert execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
164
    execute_in_page('returnval(install_view.install_but);').click()
165

    
166
    WebDriverWait(driver, 10).until(installed)
167

    
168
    assert execute_in_page('returnval(shw(4));') == [['show'], True]
169
    execute_in_page('returnval(install_view.dialog_ctx.ok_but);').click()
170
    assert execute_in_page('returnval(shw(4));') == [['show', 'hide'], False]
171

    
172
    # Verify the newer version install.
173
    old_db_contents, db_contents = db_contents, get_db_contents(execute_in_page)
174
    old_db_contents['mapping'][0]['version'][-1] += 1
175
    assert db_contents['mapping'] == old_db_contents['mapping']
176

    
177
    # All items are up to date - verify dialog is instead shown in this case.
178
    execute_in_page('install_view.show(...arguments);',
179
                    'https://hydril.la/', 'mapping', root_mapping_id)
180

    
181
    fetched = lambda d: 'Fetching ' not in containers['dialog_container'].text
182
    WebDriverWait(driver, 10).until(fetched)
183

    
184
    assert 'Nothing to do - packages already installed.' \
185
        in containers['dialog_container'].text
186
    assert_container_displayed('dialog_container')
187

    
188
    assert execute_in_page('returnval(shw(6));') == [['show'], True]
189
    execute_in_page('returnval(install_view.dialog_ctx.ok_but);').click()
190
    assert execute_in_page('returnval(shw(6));') == [['show', 'hide'], False]
191

    
192
@pytest.mark.ext_data(install_ext_data)
193
@pytest.mark.usefixtures('webextension')
194
@pytest.mark.parametrize('message', [
195
    'fetching_data',
196
    'failure_to_communicate_sendmessage',
197
    'HTTP_code_item',
198
    'invalid_JSON',
199
    'newer_API_version',
200
    'invalid_response_format',
201
    'indexeddb_error_item',
202
    'installing',
203
    'indexeddb_error_file_uses',
204
    'failure_to_communicate_fetch',
205
    'HTTP_code_file',
206
    'not_valid_text',
207
    'sha256_mismatch',
208
    'indexeddb_error_write'
209
])
210
def test_install_dialogs(driver, execute_in_page, message):
211
    """
212
    Test of various error and loading messages used in install view.
213
    """
214
    containers, assert_container_displayed = setup_view(driver, execute_in_page)
215

    
216
    def dlg_buts():
217
        return execute_in_page(
218
            '''{
219
            const dlg = install_view.dialog_ctx;
220
            const ids = ['ask_buts', 'conf_buts'];
221
            returnval(ids.filter(id => !dlg[id].classList.contains("hide")));
222
            }''')
223

    
224
    def dialog_txt():
225
        return execute_in_page(
226
            'returnval(install_view.dialog_ctx.msg.textContent);'
227
        )
228

    
229
    def assert_dlg(awaited_buttons, expected_msg, hides_install_view=True,
230
                   button_to_click='ok_but'):
231
        WebDriverWait(driver, 10).until(lambda d: dlg_buts() == awaited_buttons)
232

    
233
        assert expected_msg == dialog_txt()
234

    
235
        execute_in_page(
236
            f'returnval(install_view.dialog_ctx.{button_to_click});'
237
        ).click()
238

    
239
        if hides_install_view:
240
            assert execute_in_page('returnval(shw());') == \
241
                [['show', 'hide'], False]
242

    
243
    if message == 'fetching_data':
244
        execute_in_page(
245
            '''
246
            browser.tabs.sendMessage = () => new Promise(cb => {});
247
            install_view.show(...arguments);
248
            ''',
249
            'https://hydril.la/', 'mapping', 'mapping_a')
250

    
251
        assert dlg_buts() == []
252
        assert dialog_txt() == 'Fetching data from repository...'
253
    elif message == 'failure_to_communicate_sendmessage':
254
        execute_in_page(
255
            '''
256
            browser.tabs.sendMessage = () => Promise.resolve({error: "sth"});
257
            install_view.show(...arguments);
258
            ''',
259
            'https://hydril.la/', 'mapping', 'mapping_a')
260

    
261
        assert_dlg(['conf_buts'], 'Failure to communicate with repository :(')
262
    elif message == 'HTTP_code_item':
263
        execute_in_page(
264
            '''
265
            const response = {ok: false, status: 404};
266
            browser.tabs.sendMessage = () => Promise.resolve(response);
267
            install_view.show(...arguments);
268
            ''',
269
            'https://hydril.la/', 'mapping', 'mapping_a')
270

    
271
        assert_dlg(['conf_buts'], 'Repository sent HTTP code 404 :(')
272
    elif message == 'invalid_JSON':
273
        execute_in_page(
274
            '''
275
            const response = {ok: true, status: 200, error_json: "sth"};
276
            browser.tabs.sendMessage = () => Promise.resolve(response);
277
            install_view.show(...arguments);
278
            ''',
279
            'https://hydril.la/', 'mapping', 'mapping_a')
280

    
281
        assert_dlg(['conf_buts'], "Repository's response is not valid JSON :(")
282
    elif message == 'newer_API_version':
283
        execute_in_page(
284
            '''
285
            const response = {
286
                ok: true,
287
                status: 200,
288
                json: {$schema: "https://hydrilla.koszko.org/schemas/api_mapping_description-2.1.schema.json"}
289
            };
290
            browser.tabs.sendMessage = () => Promise.resolve(response);
291
            install_view.show(...arguments);
292
            ''',
293
            'https://hydril.la/', 'mapping', 'somemapping', [2, 1])
294

    
295
        assert_dlg(['conf_buts'],
296
                   'Mapping somemapping-2.1 was served using unsupported Hydrilla API version. You might need to update Haketilo.')
297
    elif message == 'invalid_response_format':
298
        execute_in_page(
299
            '''
300
            const response = {
301
                ok: true,
302
                status: 200,
303
                /* $schema is not a string as it should be. */
304
                json: {$schema: null}
305
            };
306
            browser.tabs.sendMessage = () => Promise.resolve(response);
307
            install_view.show(...arguments);
308
            ''',
309
            'https://hydril.la/', 'resource', 'someresource')
310

    
311
        assert_dlg(['conf_buts'],
312
                   'Resource someresource was served using a nonconforming response format.')
313
    elif message == 'indexeddb_error_item':
314
        execute_in_page(
315
            '''
316
            haketilodb.idb_get = () => {throw "some error";};
317
            install_view.show(...arguments);
318
            ''',
319
            'https://hydril.la/', 'mapping', 'mapping_a')
320

    
321
        assert_dlg(['conf_buts'],
322
                   "Error accessing Haketilo's internal database :(")
323
    elif message == 'installing':
324
        execute_in_page(
325
            '''
326
            haketilodb.save_items = () => new Promise(() => {});
327
            returnval(install_view.show(...arguments));
328
            ''',
329
            'https://hydril.la/', 'mapping', 'mapping_b')
330

    
331
        execute_in_page('returnval(install_view.install_but);').click()
332

    
333
        assert dlg_buts() == []
334
        assert dialog_txt() == 'Installing...'
335
    elif message == 'indexeddb_error_file_uses':
336
        execute_in_page(
337
            '''
338
            const old_idb_get = haketilodb.idb_get;
339
            haketilodb.idb_get = function(transaction, store_name, identifier) {
340
                if (store_name === "file_uses")
341
                    throw "some error";
342
                return old_idb_get(...arguments);
343
            }
344
            returnval(install_view.show(...arguments));
345
            ''',
346
            'https://hydril.la/', 'mapping', 'mapping_b')
347

    
348
        execute_in_page('returnval(install_view.install_but);').click()
349

    
350
        assert_dlg(['conf_buts'],
351
                   "Error accessing Haketilo's internal database :(")
352
    elif message == 'failure_to_communicate_fetch':
353
        execute_in_page(
354
            '''
355
            fetch = () => {throw "some error";};
356
            returnval(install_view.show(...arguments));
357
            ''',
358
            'https://hydril.la/', 'mapping', 'mapping_b')
359

    
360
        execute_in_page('returnval(install_view.install_but);').click()
361

    
362
        assert_dlg(['conf_buts'],
363
                   'Failure to communicate with repository :(')
364
    elif message == 'HTTP_code_file':
365
        execute_in_page(
366
            '''
367
            fetch = () => Promise.resolve({ok: false, status: 400});
368
            returnval(install_view.show(...arguments));
369
            ''',
370
            'https://hydril.la/', 'mapping', 'mapping_b')
371

    
372
        execute_in_page('returnval(install_view.install_but);').click()
373

    
374
        assert_dlg(['conf_buts'], 'Repository sent HTTP code 400 :(')
375
    elif message == 'not_valid_text':
376
        execute_in_page(
377
            '''
378
            const err = () => {throw "some error";};
379
            fetch = () => Promise.resolve({ok: true, status: 200, text: err});
380
            returnval(install_view.show(...arguments));
381
            ''',
382
            'https://hydril.la/', 'mapping', 'mapping_b')
383

    
384
        execute_in_page('returnval(install_view.install_but);').click()
385

    
386
        assert_dlg(['conf_buts'], "Repository's response is not valid text :(")
387
    elif message == 'sha256_mismatch':
388
        execute_in_page(
389
            '''
390
            let old_fetch = fetch, url_used;
391
            fetch = async function(url) {
392
                url_used = url;
393
                const response = await old_fetch(...arguments);
394
                const text = () => response.text().then(t => t + ":d");
395
                return {ok: response.ok, status: response.status, text};
396
            }
397
            returnval(install_view.show(...arguments));
398
            ''',
399
            'https://hydril.la/', 'mapping', 'mapping_b')
400

    
401
        execute_in_page('returnval(install_view.install_but);').click()
402

    
403
        get_url_used = lambda d: execute_in_page('returnval(url_used);')
404
        url_used = WebDriverWait(driver, 10).until(get_url_used)
405
        print ((url_used,))
406

    
407
        assert dlg_buts() == ['conf_buts']
408
        assert dialog_txt() == \
409
            f'{url_used} served a file with different SHA256 cryptographic sum :('
410
    elif message == 'indexeddb_error_write':
411
        execute_in_page(
412
            '''
413
            haketilodb.save_items = () => {throw "some error";};
414
            returnval(install_view.show(...arguments));
415
            ''',
416
            'https://hydril.la/', 'mapping', 'mapping_b')
417

    
418
        execute_in_page('returnval(install_view.install_but);').click()
419

    
420
        assert_dlg(['conf_buts'],
421
                   "Error writing to Haketilo's internal database :(")
422
    else:
423
        raise Exception('made a typo in test function params?')
(10-10/25)