Project

General

Profile

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

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

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
    execute_in_page(mock_cacher_code)
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
    'sha256_mismatch',
207
    'indexeddb_error_write'
208
])
209
def test_install_dialogs(driver, execute_in_page, message):
210
    """
211
    Test of various error and loading messages used in install view.
212
    """
213
    containers, assert_container_displayed = setup_view(driver, execute_in_page)
214

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

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

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

    
232
        assert expected_msg == dialog_txt()
233

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

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

    
242
    if message == 'fetching_data':
243
        execute_in_page(
244
            '''
245
            window.mock_cacher_fetch = () => new Promise(cb => {});
246
            install_view.show(...arguments);
247
            ''',
248
            'https://hydril.la/', 'mapping', 'mapping-a')
249

    
250
        assert dlg_buts() == []
251
        assert dialog_txt() == 'Fetching data from repository...'
252
    elif message == 'failure_to_communicate_sendmessage':
253
        execute_in_page(
254
            '''
255
            window.mock_cacher_fetch =
256
                () => {throw new Error("Something happened :o")};
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 = new Response("", {status: 404});
266
            window.mock_cacher_fetch = () => 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 = new Response("sth", {status: 200});
276
            window.mock_cacher_fetch = () => 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 newer_schema_url =
286
                "https://hydrilla.koszko.org/schemas/api_mapping_description-255.1.schema.json";
287
            const mocked_json_data = JSON.stringify({$schema: newer_schema_url});
288
            const response = new Response(mocked_json_data, {status: 200});
289
            window.mock_cacher_fetch = () => Promise.resolve(response);
290
            install_view.show(...arguments);
291
            ''',
292
            'https://hydril.la/', 'mapping', 'mapping-a', [2022, 5, 10])
293

    
294
        assert_dlg(['conf_buts'],
295
                   'Mapping mapping-a-2022.5.10 was served using unsupported Hydrilla API version. You might need to update Haketilo.')
296
    elif message == 'invalid_response_format':
297
        execute_in_page(
298
            '''
299
            window.mock_cacher_fetch = async function(...args) {
300
                const response = await fetch(...args);
301
                const json = await response.json();
302

    
303
                /* identifier is no longer a string as it should be. */
304
                json.identifier = 1234567;
305

    
306
                return new Response(JSON.stringify(json), {
307
                    status:     response.status,
308
                    statusText: response.statusText,
309
                    headers:    [...response.headers.entries()]
310
                });
311
            }
312
            install_view.show(...arguments);
313
            ''',
314
            'https://hydril.la/', 'resource', 'resource-a')
315

    
316
        assert_dlg(['conf_buts'],
317
                   'Resource resource-a was served using a nonconforming response format.')
318
    elif message == 'indexeddb_error_item':
319
        execute_in_page(
320
            '''
321
            haketilodb.idb_get = () => {throw "some error";};
322
            install_view.show(...arguments);
323
            ''',
324
            'https://hydril.la/', 'mapping', 'mapping-a')
325

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

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

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

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

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

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

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

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

    
379
        assert_dlg(['conf_buts'], 'Repository sent HTTP code 400 :(')
380
    elif message == 'sha256_mismatch':
381
        execute_in_page(
382
            '''
383
            let old_fetch = fetch, url_used;
384
            fetch = async function(url) {
385
                url_used = url;
386
                const response = await old_fetch(...arguments);
387
                const text = () => response.text().then(t => t + ":d");
388
                return {ok: response.ok, status: response.status, text};
389
            }
390
            returnval(install_view.show(...arguments));
391
            ''',
392
            'https://hydril.la/', 'mapping', 'mapping-b')
393

    
394
        execute_in_page('returnval(install_view.install_but);').click()
395

    
396
        get_url_used = lambda d: execute_in_page('returnval(url_used);')
397
        url_used = WebDriverWait(driver, 10).until(get_url_used)
398
        print ((url_used,))
399

    
400
        assert dlg_buts() == ['conf_buts']
401
        assert dialog_txt() == \
402
            f'{url_used} served a file with different SHA256 cryptographic sum :('
403
    elif message == 'indexeddb_error_write':
404
        execute_in_page(
405
            '''
406
            haketilodb.save_items = () => {throw "some error";};
407
            returnval(install_view.show(...arguments));
408
            ''',
409
            'https://hydril.la/', 'mapping', 'mapping-b')
410

    
411
        execute_in_page('returnval(install_view.install_but);').click()
412

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