diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/extension_crafting.py | 4 | ||||
-rw-r--r-- | test/unit/conftest.py | 14 | ||||
-rw-r--r-- | test/unit/test_basic.py | 19 | ||||
-rw-r--r-- | test/unit/test_default_policy_dialog.py | 50 | ||||
-rw-r--r-- | test/unit/test_dialog.py | 130 | ||||
-rw-r--r-- | test/unit/test_indexeddb.py | 36 | ||||
-rw-r--r-- | test/unit/test_item_list.py | 180 | ||||
-rw-r--r-- | test/unit/test_item_preview.py | 235 | ||||
-rw-r--r-- | test/unit/utils.py | 59 |
9 files changed, 684 insertions, 43 deletions
diff --git a/test/extension_crafting.py b/test/extension_crafting.py index 61f8530..efb2687 100644 --- a/test/extension_crafting.py +++ b/test/extension_crafting.py @@ -61,7 +61,7 @@ def manifest_template(): '<all_urls>', 'unlimitedStorage' ], - 'content_security_policy': "default-src 'self'; script-src 'self' https://serve.scrip.ts;", + 'content_security_policy': "object-src 'none'; script-src 'self' https://serve.scrip.ts;", 'web_accessible_resources': ['testpage.html'], 'background': { 'persistent': True, @@ -143,6 +143,8 @@ def make_extension(destination_dir, content_script=default_content_script, test_page=default_test_page, extra_files={}, extra_html=[]): + if not hasattr(extra_html, '__iter__'): + extra_html = [extra_html] manifest = manifest_template() extension_id = '{%s}' % uuid4() manifest['applications']['gecko']['id'] = extension_id diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 48e66c1..9318f6e 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -66,15 +66,25 @@ def webextension(driver, request): ext_data = request.node.get_closest_marker('ext_data') if ext_data is None: raise Exception('"webextension" fixture requires "ext_data" marker to be set') + ext_data = ext_data.args[0].copy() + + navigate_to = ext_data.get('navigate_to') + if navigate_to is not None: + del ext_data['navigate_to'] - ext_path = make_extension(Path(driver.firefox_profile.path), - **ext_data.args[0]) driver.get('https://gotmyowndoma.in/') + ext_path = make_extension(Path(driver.firefox_profile.path), **ext_data) addon_id = driver.install_addon(str(ext_path), temporary=True) WebDriverWait(driver, 10).until( EC.url_matches('^moz-extension://.*') ) + + if navigate_to is not None: + testpage_url = driver.execute_script('return window.location.href;') + driver.get(testpage_url.replace('testpage.html', navigate_to)) + yield + close_all_but_one_window(driver) driver.get('https://gotmyowndoma.in/') driver.uninstall_addon(addon_id) diff --git a/test/unit/test_basic.py b/test/unit/test_basic.py index 612fe06..5f42f5d 100644 --- a/test/unit/test_basic.py +++ b/test/unit/test_basic.py @@ -58,15 +58,14 @@ def test_webextension(driver): assert "Extension's options page for testing" in heading @pytest.mark.ext_data({ - 'extra_html': [ - ExtraHTML( - 'html/default_blocking_policy.html', - { - 'html/default_blocking_policy.js': - 'document.body.innerHTML = `ski-ba-bop-ba ${typeof by_id}`;' - } - ) - ] + 'extra_html': ExtraHTML( + 'html/default_blocking_policy.html', + { + 'html/default_blocking_policy.js': + 'document.body.innerHTML = `ski-ba-bop-ba ${typeof by_id}`;' + } + ), + 'navigate_to': 'html/default_blocking_policy.html' }) @pytest.mark.usefixtures('webextension') def test_extra_html(driver): @@ -74,7 +73,5 @@ def test_extra_html(driver): A trivial test case of the facility for loading the Haketilo's HTML files into test WebExtension for unit-testing. """ - driver.get(driver.execute_script('return window.location.href;') - .replace('testpage.html', 'html/default_blocking_policy.html')) assert driver.execute_script('return document.body.innerText') == \ 'ski-ba-bop-ba function' diff --git a/test/unit/test_default_policy_dialog.py b/test/unit/test_default_policy_dialog.py new file mode 100644 index 0000000..992b487 --- /dev/null +++ b/test/unit/test_default_policy_dialog.py @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: CC0-1.0 + +""" +Haketilo unit tests - default script blocking policy dialog +""" + +# This file is part of Haketilo +# +# Copyright (C) 2022, Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the CC0 1.0 Universal License as published by +# the Creative Commons Corporation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# CC0 1.0 Universal License for more details. + +import pytest + +from ..extension_crafting import ExtraHTML +from ..script_loader import load_script + +broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' + +@pytest.mark.ext_data({ + 'background_script': broker_js, + 'extra_html': ExtraHTML( + 'html/default_blocking_policy.html', + { + 'html/default_blocking_policy.js': + 'init_default_policy_dialog();' + } + ), + 'navigate_to': 'html/default_blocking_policy.html' +}) +@pytest.mark.usefixtures('webextension') +def test_default_blocking_policy_dialog(driver, wait_elem_text): + """ + A test case for the dialog that facilitates toggling the default policy of + script blocking. + """ + wait_elem_text('current_policy_span', 'block') + + driver.find_element_by_id('toggle_policy_but').click() + wait_elem_text('current_policy_span', 'allow') + + driver.find_element_by_id('toggle_policy_but').click() + wait_elem_text('current_policy_span', 'block') diff --git a/test/unit/test_dialog.py b/test/unit/test_dialog.py new file mode 100644 index 0000000..384a889 --- /dev/null +++ b/test/unit/test_dialog.py @@ -0,0 +1,130 @@ +# SPDX-License-Identifier: CC0-1.0 + +""" +Haketilo unit tests - showing an error/info/question dalog +""" + +# This file is part of Haketilo +# +# Copyright (C) 2022, Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the CC0 1.0 Universal License as published by +# the Creative Commons Corporation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# CC0 1.0 Universal License for more details. + +import pytest + +from ..extension_crafting import ExtraHTML +from ..script_loader import load_script + +@pytest.mark.ext_data({ + 'extra_html': ExtraHTML('html/dialog.html', {}), + 'navigate_to': 'html/dialog.html' +}) +@pytest.mark.usefixtures('webextension') +def test_dialog_show_close(driver, execute_in_page): + """ + A test case of basing dialog showing/closing. + """ + execute_in_page(load_script('html/dialog.js')) + execute_in_page( + ''' + let cb_calls, call_prom; + const dialog_context = make(() => cb_calls.push("show"), + () => cb_calls.push("hide")); + document.body.append(dialog_context.main_div); + ''') + + buts = driver.find_elements_by_tag_name('button') + buts = dict([(but.text, but) for but in buts]) + + for i, (dialog_function, button_text, expected_result) in enumerate([ + ('info', 'Ok', None), + ('error', 'Ok', None), + ('ask', 'Yes', True), + ('ask', 'No', False) + ]): + cb_calls, is_shown = execute_in_page( + f''' + cb_calls = []; + call_prom = {dialog_function}(dialog_context, + `sample_text_${{arguments[0]}}`); + returnval([cb_calls, dialog_context.shown]); + ''', + i) + assert cb_calls == ['show'] + assert is_shown == True + + page_source = driver.page_source + assert f'sample_text_{i}' in page_source + assert f'sample_text_{i - 1}' not in page_source + + assert any([not but.is_displayed() for but in buts.values()]) + + assert buts[button_text].is_displayed() + buts[button_text].click() + + cb_calls, result, is_shown = execute_in_page( + '''{ + console.error(dialog_context.msg.textContent); + const values_cb = r => [cb_calls, r, dialog_context.shown]; + returnval(call_prom.then(values_cb)); + }''') + assert cb_calls == ['show', 'hide'] + assert result == expected_result + assert is_shown == False + +@pytest.mark.ext_data({ + 'extra_html': ExtraHTML('html/dialog.html', {}), + 'navigate_to': 'html/dialog.html' +}) +@pytest.mark.usefixtures('webextension') +def test_dialog_queue(driver, execute_in_page): + """ + A test case of queuing dialog display operations. + """ + execute_in_page(load_script('html/dialog.js')) + execute_in_page( + ''' + let cb_calls = [], call_proms = []; + const dialog_context = make(() => cb_calls.push("show"), + () => cb_calls.push("hide")); + document.body.append(dialog_context.main_div); + ''') + + buts = driver.find_elements_by_tag_name('button') + buts = dict([(but.text, but) for but in buts]) + + for i in range(5): + cb_calls, is_shown, msg_elem = execute_in_page( + ''' + call_proms.push(ask(dialog_context, "somequestion" + arguments[0])); + returnval([cb_calls, dialog_context.shown, dialog_context.msg]); + ''', + i) + assert cb_calls == ['show'] + assert is_shown == True + assert msg_elem.text == 'somequestion0' + + for i in range(5): + buts['Yes' if i & 1 else 'No'].click() + cb_calls, is_shown, msg_elem, result = execute_in_page( + '''{ + const values_cb = + r => [cb_calls, dialog_context.shown, dialog_context.msg, r]; + returnval(call_proms.splice(0, 1)[0].then(values_cb)); + }''') + if i < 4: + assert cb_calls == ['show'] + assert is_shown == True + assert msg_elem.text == f'somequestion{i + 1}' + else: + assert cb_calls == ['show', 'hide'] + assert is_shown == False + + assert result == bool(i & 1) diff --git a/test/unit/test_indexeddb.py b/test/unit/test_indexeddb.py index 447ee6e..9dfbe63 100644 --- a/test/unit/test_indexeddb.py +++ b/test/unit/test_indexeddb.py @@ -19,62 +19,41 @@ Haketilo unit tests - IndexedDB access import pytest import json -from hashlib import sha256 from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import WebDriverException from ..script_loader import load_script +from .utils import sample_files, sample_files_by_hash, sample_file_ref indexeddb_js = lambda: load_script('common/indexeddb.js') broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' -def sample_file(contents): - return { - 'hash_key': f'sha256-{sha256(contents.encode()).digest().hex()}', - 'contents': contents - } - -sample_files = { - 'report.spdx': sample_file('<!-- dummy report -->'), - 'LICENSES/somelicense.txt': sample_file('Permission is granted...'), - 'hello.js': sample_file('console.log("hello!");\n'), - 'bye.js': sample_file('console.log("bye!");\n'), - 'combined.js': sample_file('console.log("hello!\\nbye!");\n'), - 'README.md': sample_file('# Python Frobnicator\n...') -} - -sample_files_by_hash = dict([[file['hash_key'], file['contents']] - for file in sample_files.values()]) - # Sample resource definitions. They'd normally contain more fields but here we # use simplified versions. def make_sample_resource(): return { 'source_copyright': [ - file_ref('report.spdx'), - file_ref('LICENSES/somelicense.txt') + sample_file_ref('report.spdx'), + sample_file_ref('LICENSES/somelicense.txt') ], 'type': 'resource', 'identifier': 'helloapple', - 'scripts': [file_ref('hello.js'), file_ref('bye.js')] + 'scripts': [sample_file_ref('hello.js'), sample_file_ref('bye.js')] } def make_sample_mapping(): return { 'source_copyright': [ - file_ref('report.spdx'), - file_ref('README.md') + sample_file_ref('report.spdx'), + sample_file_ref('README.md') ], 'type': 'mapping', 'identifier': 'helloapple' } -def file_ref(file_name): - return {'file': file_name, 'hash_key': sample_files[file_name]['hash_key']} - def clear_indexeddb(execute_in_page): execute_in_page( '''{ @@ -168,7 +147,7 @@ def test_haketilodb_item_modifications(driver, execute_in_page): # See if trying to add an item without providing all its files ends in an # exception and aborts the transaction as it should. - sample_item['scripts'].append(file_ref('combined.js')) + sample_item['scripts'].append(sample_file_ref('combined.js')) incomplete_files = {**sample_files_by_hash} incomplete_files.pop(sample_files['combined.js']['hash_key']) exception = execute_in_page( @@ -439,7 +418,6 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text): ''' function update_item(store_name, change) { - console.log('# update', ...arguments); const elem_id = `${store_name}_${change.key}`; let elem = document.getElementById(elem_id); elem = elem || document.createElement("li"); diff --git a/test/unit/test_item_list.py b/test/unit/test_item_list.py new file mode 100644 index 0000000..3aba006 --- /dev/null +++ b/test/unit/test_item_list.py @@ -0,0 +1,180 @@ +# SPDX-License-Identifier: CC0-1.0 + +""" +Haketilo unit tests - displaying list of resources/mappings +""" + +# This file is part of Haketilo +# +# Copyright (C) 2022, Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the CC0 1.0 Universal License as published by +# the Creative Commons Corporation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# CC0 1.0 Universal License for more details. + +import pytest +from selenium.webdriver.support.ui import WebDriverWait + +from ..extension_crafting import ExtraHTML +from ..script_loader import load_script +from .utils import sample_files, sample_files_by_hash, sample_file_ref, \ + item_version_string + +broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' + +def make_sample_mapping(identifier, long_name): + return { + 'source_name': 'example-org-fixes-new', + 'source_copyright': [ + sample_file_ref('report.spdx'), + sample_file_ref('LICENSES/CC0-1.0.txt') + ], + 'type': 'mapping', + 'identifier': identifier, + 'long_name': long_name, + 'uuid': '54d23bba-472e-42f5-9194-eaa24c0e3ee7', + 'version': [2022, 5, 10], + 'description': 'suckless something something', + 'payloads': { + 'https://example.org/a/*': { + 'identifier': 'some-KISS-resource' + }, + 'https://example.org/t/*': { + 'identifier': 'another-KISS-resource' + } + } + } + +def make_sample_resource(identifier, long_name): + return { + 'source_name': 'hello', + 'source_copyright': [ + sample_file_ref('report.spdx'), + sample_file_ref('LICENSES/CC0-1.0.txt') + ], + 'type': 'resource', + 'identifier': identifier, + 'long_name': long_name, + 'uuid': 'a6754dcb-58d8-4b7a-a245-24fd7ad4cd68', + 'version': [2021, 11, 10], + 'revision': 1, + 'description': 'greets an apple', + 'dependencies': ['hello-message'], + 'scripts': [ + sample_file_ref('hello.js'), + sample_file_ref('bye.js') + ] + } + +@pytest.mark.ext_data({ + 'background_script': broker_js, + 'extra_html': ExtraHTML('html/item_list.html', {}), + 'navigate_to': 'html/item_list.html' +}) +@pytest.mark.usefixtures('webextension') +@pytest.mark.parametrize('item_type', ['resource', 'mapping']) +def test_item_list_ordering(driver, execute_in_page, item_type): + """ + A test case of items list proper ordering. + """ + execute_in_page(load_script('html/item_list.js')) + + make_item = make_sample_resource if item_type == 'resource' \ + else make_sample_mapping + + # Choose sample long names so as to test automatic sorting of items. + long_names = ['sample', 'sample it', 'Sample it', 'SAMPLE IT', + 'test', 'test it', 'Test it', 'TEST IT'] + # Let's operate on a reverse-sorted copy + long_names_reversed = [*long_names] + long_names_reversed.reverse() + + items = [make_item(f'it_{hex(2 * i + copy)[-1]}', name) + for i, name in enumerate(long_names_reversed) + for copy in (1, 0)] + # When adding/updating items this item will be updated at the end and this + # last update will be used to verify that a set of opertions completed. + extra_item = make_item('extraitem', 'extra item') + extra_dict = {'extraitem': {item_version_string(extra_item): extra_item}} + + # After this reversal items are sorted in the exact order they are expected + # to appear in the HTML list. + items.reverse() + + sample_data = { + 'resources': {}, + 'mappings': {}, + 'files': sample_files_by_hash + } + + def is_prime(n): + return n > 1 and all([n % i != 0 for i in range(2, n)]) + + indexes_added = set() + for iteration, to_include in enumerate([ + set([i for i in range(len(items)) if is_prime(i)]), + set([i for i in range(len(items)) + if not is_prime(i) and i & 1]), + set([i for i in range(len(items)) if i % 3 == 0]), + set([i for i in range(len(items)) + if i % 3 and not i & 1 and not is_prime(i)]), + set(range(16)) + ]): + # On the last iteration, re-add ALL items but with changed names. + if len(to_include) == 16: + for it in items: + it['long_name'] = f'somewhat renamed {it["long_name"]}' + + items_to_inclue = [items[i] for i in sorted(to_include)] + sample_data[item_type + 's'] = \ + dict([(it['identifier'], {item_version_string(it): it}) + for it in items_to_inclue]) + execute_in_page('returnval(haketilodb.save_items(arguments[0]));', + sample_data) + + extra_item['long_name'] = f'{iteration} {extra_item["long_name"]}' + sample_data[item_type + 's'] = extra_dict + execute_in_page('returnval(haketilodb.save_items(arguments[0]));', + sample_data) + + if iteration == 0: + execute_in_page( + f''' + let list_ctx, items = arguments[0]; + async function create_list() {{ + list_ctx = await {item_type}_list(); + document.body.append(list_ctx.main_div); + }} + returnval(create_list()); + ''') + + def lis_ready(driver): + return extra_item['long_name'] == execute_in_page( + 'returnval(list_ctx.ul.firstElementChild.textContent);' + ) + + indexes_added.update(to_include) + WebDriverWait(driver, 10).until(lis_ready) + + li_texts = execute_in_page( + ''' + var lis = [...list_ctx.ul.children].slice(1); + returnval(lis.map(li => li.textContent)); + ''') + assert li_texts == [items[i]['long_name'] for i in indexes_added] + + preview_texts = execute_in_page( + '''{ + const get_texts = + li => [li.click(), list_ctx.preview_container.textContent][1]; + returnval(lis.map(get_texts)); + }''') + + for i, text in zip(sorted(indexes_added), preview_texts): + assert items[i]['identifier'] in text + assert items[i]['long_name'] in text diff --git a/test/unit/test_item_preview.py b/test/unit/test_item_preview.py new file mode 100644 index 0000000..887e4f4 --- /dev/null +++ b/test/unit/test_item_preview.py @@ -0,0 +1,235 @@ +# SPDX-License-Identifier: CC0-1.0 + +""" +Haketilo unit tests - displaying resources and mappings details +""" + +# This file is part of Haketilo +# +# Copyright (C) 2022, Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the CC0 1.0 Universal License as published by +# the Creative Commons Corporation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# CC0 1.0 Universal License for more details. + +import pytest +from selenium.webdriver.support.ui import WebDriverWait + +from ..extension_crafting import ExtraHTML +from ..script_loader import load_script +from .utils import * + +broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' + +def make_sample_mapping(): + return { + 'source_name': 'example-org-fixes-new', + 'source_copyright': [ + sample_file_ref('report.spdx'), + sample_file_ref('LICENSES/CC0-1.0.txt') + ], + 'type': 'mapping', + 'identifier': 'example-org-minimal', + 'long_name': 'Example.org Minimal', + 'uuid': '54d23bba-472e-42f5-9194-eaa24c0e3ee7', + 'version': [2022, 5, 10], + 'description': 'suckless something something', + 'payloads': { + 'https://example.org/a/*': { + 'identifier': 'some-KISS-resource' + }, + 'https://example.org/t/*': { + 'identifier': 'another-KISS-resource' + } + } + } + +def make_sample_resource(): + return { + 'source_name': 'hello', + 'source_copyright': [ + sample_file_ref('report.spdx'), + sample_file_ref('LICENSES/CC0-1.0.txt') + ], + 'type': 'resource', + 'identifier': 'helloapple', + 'long_name': 'Hello Apple', + 'uuid': 'a6754dcb-58d8-4b7a-a245-24fd7ad4cd68', + 'version': [2021, 11, 10], + 'revision': 1, + 'description': 'greets an apple', + 'dependencies': ['hello-message'], + 'scripts': [ + sample_file_ref('hello.js'), + sample_file_ref('bye.js') + ] + } + +@pytest.mark.ext_data({ + 'extra_html': ExtraHTML('html/item_preview.html', {}), + 'navigate_to': 'html/item_preview.html' +}) +@pytest.mark.usefixtures('webextension') +def test_resource_preview(driver, execute_in_page): + """ + A test case of the resource preview display function. + """ + execute_in_page(load_script('html/item_preview.js')) + + sample_resource = make_sample_resource() + + preview_div = execute_in_page( + ''' + let preview_object = resource_preview(arguments[0]); + document.body.append(preview_object.main_div); + returnval(preview_object.main_div); + ''', + sample_resource) + text = preview_div.text + + assert '...' not in text + + for string in [ + *filter(lambda v: type(v) is str, sample_resource.values()), + *sample_resource['dependencies'], + *[c['file'] for k in ('source_copyright', 'scripts') + for c in sample_resource[k]], + item_version_string(sample_resource, True) + ]: + assert string in text + + sample_resource['identifier'] = 'hellopear' + sample_resource['long_name'] = 'Hello Pear' + sample_resource['description'] = 'greets a pear' + sample_resource['dependencies'] = ['hello-msg'], + for key in ('scripts', 'source_copyright'): + for file_ref in sample_resource[key]: + file_ref['file'] = file_ref['file'].replace('.', '_') + + preview_div = execute_in_page( + ''' + returnval(resource_preview(arguments[0], preview_object).main_div); + ''', + sample_resource) + text = preview_div.text + + for string in ['...', 'pple', 'hello-message', 'report.spdx', + 'LICENSES/CC0-1.0.txt', 'hello.js', 'bye.js']: + assert string not in text + + for string in ['hellopear', 'Hello Pear', 'hello-msg', 'greets a pear', + 'report_spdx', 'LICENSES/CC0-1_0_txt', 'hello_js', 'bye_js']: + assert string in text + +@pytest.mark.ext_data({ + 'extra_html': ExtraHTML('html/item_preview.html', {}), + 'navigate_to': 'html/item_preview.html' +}) +@pytest.mark.usefixtures('webextension') +def test_mapping_preview(driver, execute_in_page): + """ + A test case of the mapping preview display function. + """ + execute_in_page(load_script('html/item_preview.js')) + + sample_mapping = make_sample_mapping() + + preview_div = execute_in_page( + ''' + let preview_object = mapping_preview(arguments[0]); + document.body.append(preview_object.main_div); + returnval(preview_object.main_div); + ''', + sample_mapping) + text = preview_div.text + + assert '...' not in text + + for string in [ + *filter(lambda v: type(v) is str, sample_mapping.values()), + *[p['identifier'] for p in sample_mapping['payloads'].values()], + *[c['file'] for c in sample_mapping['source_copyright']], + item_version_string(sample_mapping) + ]: + assert string in text + + sample_mapping['identifier'] = 'example-org-bloated' + sample_mapping['long_name'] = 'Example.org Bloated', + sample_mapping['payloads'] = dict( + [(pat.replace('.org', '.com'), res_id) + for pat, res_id in sample_mapping['payloads'].items()] + ) + for file_ref in sample_mapping['source_copyright']: + file_ref['file'] = file_ref['file'].replace('.', '_') + + preview_div = execute_in_page( + ''' + returnval(mapping_preview(arguments[0], preview_object).main_div); + ''', + sample_mapping) + text = preview_div.text + + for string in ['...', 'inimal', 'example.org', 'report.spdx', + 'LICENSES/CC0-1.0.txt']: + assert string not in text + + for string in ['example-org-bloated', 'Example.org Bloated', 'example.com', + 'report_spdx', 'LICENSES/CC0-1_0_txt']: + assert string in text + +@pytest.mark.ext_data({ + 'background_script': broker_js, + 'extra_html': ExtraHTML('html/item_preview.html', {}), + 'navigate_to': 'html/item_preview.html' +}) +@pytest.mark.usefixtures('webextension') +def test_file_preview_link(driver, execute_in_page): + """ + A test case of <a> links created by preview functions that allow a + referenced file to be previewed. + """ + execute_in_page(load_script('html/item_preview.js')) + # Mock dialog + execute_in_page('dialog.error = (...args) => window.error_args = args;') + + sample_resource = make_sample_resource() + sample_data = { + 'resources': { + sample_resource['identifier']: { + item_version_string(sample_resource): sample_resource + } + }, + 'mappings': { + }, + 'files': sample_files_by_hash + } + execute_in_page('returnval(haketilodb.save_items(arguments[0]));', + sample_data) + + # Cause the "link" to `bye.js` to be invalid. + sample_resource['scripts'][1]['hash_key'] = 'dummy nonexistent key' + + execute_in_page( + ''' + let resource_preview_object = + resource_preview(arguments[0], undefined, "dummy dialog ctx"); + document.body.append(resource_preview_object.main_div); + ''', + sample_resource) + + window0 = driver.window_handles[0] + driver.find_element_by_link_text('hello.js').click() + WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1) + window1 = [wh for wh in driver.window_handles if wh != window0][0] + driver.switch_to.window(window1) + assert sample_files['hello.js']['contents'] in driver.page_source + + driver.switch_to.window(window0) + driver.find_element_by_link_text('bye.js').click() + assert driver.execute_script('return window.error_args;') == \ + ['dummy dialog ctx', "File missing from Haketilo's inernal database :("] diff --git a/test/unit/utils.py b/test/unit/utils.py new file mode 100644 index 0000000..e2d89b9 --- /dev/null +++ b/test/unit/utils.py @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +""" +Various functions and objects that can be reused between unit tests +""" + +# This file is part of Haketilo. +# +# Copyright (C) 2021,2022 Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# I, Wojtek Kosior, thereby promise not to sue for violation of this file's +# license. Although I request that you do not make use of this code in a +# proprietary program, I am not going to enforce this in court. + +from hashlib import sha256 + +def sample_file(contents): + return { + 'hash_key': f'sha256-{sha256(contents.encode()).digest().hex()}', + 'contents': contents + } + +sample_files = { + 'report.spdx': sample_file('<!-- dummy report -->'), + 'LICENSES/somelicense.txt': sample_file('Permission is granted...'), + 'LICENSES/CC0-1.0.txt': sample_file('Dummy Commons...'), + 'hello.js': sample_file('console.log("uńićódę hello!");\n'), + 'bye.js': sample_file('console.log("bye!");\n'), + 'combined.js': sample_file('console.log("hello!\\nbye!");\n'), + 'README.md': sample_file('# Python Frobnicator\n...') +} + +sample_files_by_hash = dict([[file['hash_key'], file['contents']] + for file in sample_files.values()]) + +def sample_file_ref(file_name): + return {'file': file_name, 'hash_key': sample_files[file_name]['hash_key']} + +def item_version_string(definition, include_revision=False): + """ + Given a resource or mapping definition, read its "version" property (and + also "revision" if applicable) and produce a corresponding version string. + """ + ver = '.'.join([str(num) for num in definition['version']]) + revision = definition.get('revision') if include_revision else None + return f'{ver}-{revision}' if revision is not None else ver |