aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/extension_crafting.py4
-rw-r--r--test/unit/conftest.py14
-rw-r--r--test/unit/test_basic.py19
-rw-r--r--test/unit/test_default_policy_dialog.py50
-rw-r--r--test/unit/test_dialog.py130
-rw-r--r--test/unit/test_indexeddb.py36
-rw-r--r--test/unit/test_item_list.py180
-rw-r--r--test/unit/test_item_preview.py235
-rw-r--r--test/unit/utils.py59
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