Project

General

Profile

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

haketilo / test / unit / test_install.py @ 7218849a

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 content_script():
29
    script = load_script('content/repo_query_cacher.js')
30
    return f'{script}; {tab_id_asker}; start();'
31

    
32
def background_script():
33
    script = load_script('background/broadcast_broker.js',
34
                         '#IMPORT background/CORS_bypass_server.js')
35
    return f'{script}; {tab_id_responder}; start(); CORS_bypass_server.start();'
36

    
37
def setup_view(driver, execute_in_page):
38
    tab_id = run_content_script_in_new_window(driver, 'https://gotmyowndoma.in')
39

    
40
    execute_in_page(load_script('html/install.js'))
41
    container_ids, containers_objects = execute_in_page(
42
        '''
43
        const cb_calls = [];
44
        const install_view = new InstallView(arguments[0],
45
                                             () => cb_calls.push("show"),
46
                                             () => cb_calls.push("hide"));
47
        document.body.append(install_view.main_div);
48
        const ets = () => install_view.item_entries;
49
        const shw = slice => [cb_calls.slice(slice || 0), install_view.shown];
50
        returnval([container_ids, container_ids.map(cid => install_view[cid])]);
51
        ''',
52
        tab_id)
53

    
54
    containers = dict(zip(container_ids, containers_objects))
55

    
56
    def assert_container_displayed(container_id):
57
        for cid, cobj in zip(container_ids, containers_objects):
58
            assert (cid == container_id) == cobj.is_displayed()
59

    
60
    return containers, assert_container_displayed
61

    
62
install_ext_data = {
63
    'content_script': content_script,
64
    'background_script': background_script,
65
    'extra_html': ExtraHTML('html/install.html', {}),
66
    'navigate_to': 'html/install.html'
67
}
68

    
69
@pytest.mark.ext_data(install_ext_data)
70
@pytest.mark.usefixtures('webextension')
71
@pytest.mark.parametrize('complex_variant', [False, True])
72
def test_install_normal_usage(driver, execute_in_page, complex_variant):
73
    """
74
    Test of the normal package installation procedure with one mapping and,
75
    depending on parameter, one or many resources.
76
    """
77
    containers, assert_container_displayed = setup_view(driver, execute_in_page)
78

    
79
    assert execute_in_page('returnval(shw());') == [[], False]
80

    
81
    if complex_variant:
82
        # The resource/mapping others depend on.
83
        root_id = 'abcd-defg-ghij'
84
        root_resource_id = f'resource_{root_id}'
85
        root_mapping_id = f'mapping_{root_id}'
86
        # Those ids are used to check the alphabetical ordering.
87
        resource_ids = [f'resource_{letters}' for letters in (
88
            'a', 'abcd', root_id, 'b', 'c',
89
            'd', 'defg', 'e', 'f',
90
            'g', 'ghij', 'h', 'i', 'j'
91
        )]
92
        files_count = 9
93
    else:
94
        root_resource_id = f'resource_a'
95
        root_mapping_id = f'mapping_a'
96
        resource_ids = [root_resource_id]
97
        files_count = 0
98

    
99
    # Preview the installation of a resource, show resource's details, close
100
    # the details and cancel installation.
101
    execute_in_page('returnval(install_view.show(...arguments));',
102
                    'https://hydril.la/', 'resource', root_resource_id)
103

    
104
    assert execute_in_page('returnval(shw());') == [['show'], True]
105
    assert f'{root_resource_id}-2021.11.11-1'\
106
        in containers['install_preview'].text
107
    assert_container_displayed('install_preview')
108

    
109
    entries = execute_in_page('returnval(ets().map(e => e.main_li.innerText));')
110
    assert len(entries) == len(resource_ids)
111
    # Verify alphabetical ordering.
112
    assert all([id in text for id, text in zip(resource_ids, entries)])
113

    
114
    assert not execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
115
    execute_in_page('returnval(ets()[0].details_but);').click()
116
    assert 'resource_a' in containers['resource_preview_container'].text
117
    assert_container_displayed('resource_preview_container')
118

    
119
    execute_in_page('returnval(install_view.resource_back_but);').click()
120
    assert_container_displayed('install_preview')
121

    
122
    assert execute_in_page('returnval(shw());') == [['show'], True]
123
    execute_in_page('returnval(install_view.cancel_but);').click()
124
    assert execute_in_page('returnval(shw());') == [['show', 'hide'], False]
125

    
126
    # Preview the installation of a mapping and a resource, show mapping's
127
    # details, close the details and commit the installation.
128
    execute_in_page('returnval(install_view.show(...arguments));',
129
                    'https://hydril.la/', 'mapping',
130
                    root_mapping_id, [2022, 5, 10])
131

    
132
    assert execute_in_page('returnval(shw(2));') == [['show'], True]
133
    assert_container_displayed('install_preview')
134

    
135
    entries = execute_in_page('returnval(ets().map(e => e.main_li.innerText));')
136
    assert len(entries) == len(resource_ids) + 1
137
    assert f'{root_mapping_id}-2022.5.10' in entries[0]
138
    # Verify alphabetical ordering.
139
    assert all([id in text for id, text in zip(resource_ids, entries[1:])])
140

    
141
    assert not execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
142
    execute_in_page('returnval(ets()[0].details_but);').click()
143
    assert root_mapping_id in containers['mapping_preview_container'].text
144
    assert_container_displayed('mapping_preview_container')
145

    
146
    execute_in_page('returnval(install_view.mapping_back_but);').click()
147
    assert_container_displayed('install_preview')
148

    
149
    execute_in_page('returnval(install_view.install_but);').click()
150
    installed = lambda d: 'ly installed!' in containers['dialog_container'].text
151
    WebDriverWait(driver, 10000).until(installed)
152

    
153
    assert execute_in_page('returnval(shw(2));') == [['show'], True]
154
    execute_in_page('returnval(install_view.dialog_ctx.ok_but);').click()
155
    assert execute_in_page('returnval(shw(2));') == [['show', 'hide'], False]
156

    
157
    # Verify the install
158
    db_contents = get_db_contents(execute_in_page)
159
    for item_type, ids in \
160
        [('mapping', {root_mapping_id}), ('resource', set(resource_ids))]:
161
        assert set([it['identifier'] for it in db_contents[item_type]]) == ids
162

    
163
    assert all([len(db_contents[store]) == files_count
164
                for store in ('files', 'file_uses')])
165

    
166
    # Update the installed mapping to a newer version.
167
    execute_in_page('returnval(install_view.show(...arguments));',
168
                    'https://hydril.la/', 'mapping', root_mapping_id)
169
    assert execute_in_page('returnval(shw(4));') == [['show'], True]
170
    # resources are already in the newest versions, hence they should not appear
171
    # in the install preview list.
172
    assert execute_in_page('returnval(ets().length);') == 1
173
    # Mapping's version update information should be displayed.
174
    assert execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
175
    execute_in_page('returnval(install_view.install_but);').click()
176

    
177
    WebDriverWait(driver, 10).until(installed)
178

    
179
    assert execute_in_page('returnval(shw(4));') == [['show'], True]
180
    execute_in_page('returnval(install_view.dialog_ctx.ok_but);').click()
181
    assert execute_in_page('returnval(shw(4));') == [['show', 'hide'], False]
182

    
183
    # Verify the newer version install.
184
    old_db_contents, db_contents = db_contents, get_db_contents(execute_in_page)
185
    old_db_contents['mapping'][0]['version'][-1] += 1
186
    assert db_contents['mapping'] == old_db_contents['mapping']
187

    
188
    # All items are up to date - verify dialog is instead shown in this case.
189
    execute_in_page('install_view.show(...arguments);',
190
                    'https://hydril.la/', 'mapping', root_mapping_id)
191
    assert execute_in_page('returnval(shw(6));') == [['show'], True]
192
    assert_container_displayed('dialog_container')
193
    assert 'Nothing to do - packages already installed.' \
194
        in containers['dialog_container'].text
195
    execute_in_page('returnval(install_view.dialog_ctx.ok_but);').click()
196
    assert execute_in_page('returnval(shw(6));') == [['show', 'hide'], False]
197

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

    
222
    def dlg_buts():
223
        return execute_in_page(
224
            '''{
225
            const dlg = install_view.dialog_ctx;
226
            const ids = ['ask_buts', 'conf_buts'];
227
            returnval(ids.filter(id => !dlg[id].classList.contains("hide")));
228
            }''')
229

    
230
    def dialog_txt():
231
        return execute_in_page(
232
            'returnval(install_view.dialog_ctx.msg.textContent);'
233
        )
234

    
235
    def assert_dlg(awaited_buttons, expected_msg, hides_install_view=True,
236
                   button_to_click='ok_but'):
237
        WebDriverWait(driver, 10).until(lambda d: dlg_buts() == awaited_buttons)
238

    
239
        assert expected_msg == dialog_txt()
240

    
241
        execute_in_page(
242
            f'returnval(install_view.dialog_ctx.{button_to_click});'
243
        ).click()
244

    
245
        if hides_install_view:
246
            assert execute_in_page('returnval(shw());') == \
247
                [['show', 'hide'], False]
248

    
249
    if message == 'fetching_data':
250
        execute_in_page(
251
            '''
252
            browser.tabs.sendMessage = () => new Promise(cb => {});
253
            install_view.show(...arguments);
254
            ''',
255
            'https://hydril.la/', 'mapping', 'mapping_a')
256

    
257
        assert dlg_buts() == []
258
        assert dialog_txt() == 'Fetching data from repository...'
259
    elif message == 'failure_to_communicate_sendmessage':
260
        execute_in_page(
261
            '''
262
            browser.tabs.sendMessage = () => Promise.resolve({error: "sth"});
263
            install_view.show(...arguments);
264
            ''',
265
            'https://hydril.la/', 'mapping', 'mapping_a')
266

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

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

    
287
        assert_dlg(['conf_buts'], "Repository's response is not valid JSON :(")
288
    elif message == 'newer_API_version':
289
        execute_in_page(
290
            '''
291
            const response = {
292
                ok: true,
293
                status: 200,
294
                json: {api_schema_version: [99, 99]}
295
            };
296
            browser.tabs.sendMessage = () => Promise.resolve(response);
297
            install_view.show(...arguments);
298
            ''',
299
            'https://hydril.la/', 'mapping', 'somemapping', [2, 1])
300

    
301
        assert_dlg(['conf_buts'],
302
                   'Mapping somemapping-2.1 was served using unsupported Hydrilla API version (99.99). You might need to update Haketilo.')
303
    elif message == 'invalid_API_version':
304
        execute_in_page(
305
            '''
306
            const response = {
307
                ok: true,
308
                status: 200,
309
                /* API version here is not an array as it should be. */
310
                json: {api_schema_version: 123}
311
            };
312
            browser.tabs.sendMessage = () => Promise.resolve(response);
313
            install_view.show(...arguments);
314
            ''',
315
            'https://hydril.la/', 'resource', 'someresource')
316

    
317
        assert_dlg(['conf_buts'],
318
                   'Resource someresource was served using unsupported Hydrilla API version. You might need to update Haketilo.')
319
    elif message == 'indexeddb_error_item':
320
        execute_in_page(
321
            '''
322
            haketilodb.idb_get = () => {throw "some error";};
323
            install_view.show(...arguments);
324
            ''',
325
            'https://hydril.la/', 'mapping', 'mapping_a')
326

    
327
        assert_dlg(['conf_buts'],
328
                   "Error accessing Haketilo's internal database :(")
329
    elif message == 'installing':
330
        execute_in_page(
331
            '''
332
            haketilodb.save_items = () => new Promise(() => {});
333
            returnval(install_view.show(...arguments));
334
            ''',
335
            'https://hydril.la/', 'mapping', 'mapping_b')
336

    
337
        execute_in_page('returnval(install_view.install_but);').click()
338

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

    
354
        execute_in_page('returnval(install_view.install_but);').click()
355

    
356
        assert_dlg(['conf_buts'],
357
                   "Error accessing Haketilo's internal database :(")
358
    elif message == 'failure_to_communicate_fetch':
359
        execute_in_page(
360
            '''
361
            fetch = () => {throw "some error";};
362
            returnval(install_view.show(...arguments));
363
            ''',
364
            'https://hydril.la/', 'mapping', 'mapping_b')
365

    
366
        execute_in_page('returnval(install_view.install_but);').click()
367

    
368
        assert_dlg(['conf_buts'],
369
                   'Failure to communicate with repository :(')
370
    elif message == 'HTTP_code_file':
371
        execute_in_page(
372
            '''
373
            fetch = () => Promise.resolve({ok: false, status: 400});
374
            returnval(install_view.show(...arguments));
375
            ''',
376
            'https://hydril.la/', 'mapping', 'mapping_b')
377

    
378
        execute_in_page('returnval(install_view.install_but);').click()
379

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

    
390
        execute_in_page('returnval(install_view.install_but);').click()
391

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

    
407
        execute_in_page('returnval(install_view.install_but);').click()
408

    
409
        get_url_used = lambda d: execute_in_page('returnval(url_used);')
410
        url_used = WebDriverWait(driver, 10).until(get_url_used)
411
        print ((url_used,))
412

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

    
424
        execute_in_page('returnval(install_view.install_but);').click()
425

    
426
        assert_dlg(['conf_buts'],
427
                   "Error writing to Haketilo's internal database :(")
428
    else:
429
        raise Exception('made a typo in test function params?')
(9-9/22)