aboutsummaryrefslogtreecommitdiff
path: root/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/test_content.py119
-rw-r--r--test/unit/test_patterns_query_manager.py44
-rw-r--r--test/unit/test_popup.py9
-rw-r--r--test/unit/test_webrequest.py6
4 files changed, 160 insertions, 18 deletions
diff --git a/test/unit/test_content.py b/test/unit/test_content.py
new file mode 100644
index 0000000..c8e0987
--- /dev/null
+++ b/test/unit/test_content.py
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: CC0-1.0
+
+"""
+Haketilo unit tests - main content script
+"""
+
+# 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
+import json
+from selenium.webdriver.support.ui import WebDriverWait
+
+from ..script_loader import load_script
+
+# From:
+# https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contentScripts/register
+# it is unclear whether the dynamically-registered content script is guaranteed
+# to be always executed after statically-registered ones. We want to test both
+# cases, so we'll make the mocked dynamic content script execute before
+# content.js on http:// pages and after it on https:// pages.
+dynamic_script = \
+ ''';
+ this.haketilo_secret = "abracadabra";
+ this.haketilo_pattern_tree = {};
+ this.haketilo_defualt_allow = false;
+
+ if (this.haketilo_content_script_main)
+ this.haketilo_content_script_main();
+ '''
+
+content_script = \
+ '''
+ /* Mock dynamic content script - case 'before'. */
+ if (/#dynamic_before$/.test(document.URL)) {
+ %s;
+ }
+
+ /* Place amalgamated content.js here. */
+ %s;
+
+ /* Rest of mocks */
+ const data_to_verify = {};
+ function data_set(prop, val) {
+ data_to_verify[prop] = val;
+ window.wrappedJSObject.data_to_verify = JSON.stringify(data_to_verify);
+ }
+
+ repo_query_cacher.start = () => data_set("cacher_started", true);
+
+ enforce_blocking = policy => data_set("enforcing", policy);
+
+ browser.runtime.onMessage.addListener = async function (listener_cb) {
+ await new Promise(cb => setTimeout(cb, 0));
+
+ /* Mock a good request. */
+ const set_good = val => data_set("good_request_result", val);
+ listener_cb(["page_info"], {}, val => set_good(val));
+
+ /* Mock a bad request. */
+ const set_bad = val => data_set("bad_request_result", val);
+ listener_cb(["???"], {}, val => set_bad(val));
+ }
+
+ /* main() call - normally present in content.js, inside '#IF !UNIT_TEST'. */
+ main();
+
+ /* Mock dynamic content script - case 'after'. */
+ if (/#dynamic_after$/.test(document.URL)) {
+ %s;
+ }
+
+ data_set("script_run_without_errors", true);
+ ''' % (dynamic_script, load_script('content/content.js'), dynamic_script)
+
+@pytest.mark.ext_data({'content_script': content_script})
+@pytest.mark.usefixtures('webextension')
+@pytest.mark.parametrize('target', ['dynamic_before', 'dynamic_after'])
+def test_content_unprivileged_page(driver, execute_in_page, target):
+ """
+ Test functioning of content.js on an page using unprivileged schema (e.g.
+ 'https://' and not 'about:').
+ """
+ driver.get(f'https://gotmyowndoma.in/index.html#{target}')
+ data = json.loads(driver.execute_script('return window.data_to_verify;'))
+
+ assert 'gotmyowndoma.in' in data['good_request_result']['url']
+ assert 'bad_request_result' not in data
+
+ assert data['cacher_started'] == True
+
+ assert data['enforcing']['allow'] == False
+ assert 'mapping' not in data['enforcing']
+ assert 'error' not in data['enforcing']
+
+ assert data['script_run_without_errors'] == True
+
+@pytest.mark.ext_data({'content_script': content_script})
+@pytest.mark.usefixtures('webextension')
+@pytest.mark.parametrize('target', ['dynamic_before', 'dynamic_after'])
+def test_content_privileged_page(driver, execute_in_page, target):
+ """
+ Test functioning of content.js on an page considered privileged (e.g. a
+ directory listing at 'file:///').
+ """
+ driver.get(f'file:///#{target}')
+ data = json.loads(driver.execute_script('return window.data_to_verify;'))
+
+ assert data == {'script_run_without_errors': True}
diff --git a/test/unit/test_patterns_query_manager.py b/test/unit/test_patterns_query_manager.py
index 4e6d1bf..c6ebb81 100644
--- a/test/unit/test_patterns_query_manager.py
+++ b/test/unit/test_patterns_query_manager.py
@@ -18,7 +18,6 @@ Haketilo unit tests - building pattern tree and putting it in a content script
# CC0 1.0 Universal License for more details.
import pytest
-import re
import json
from selenium.webdriver.support.ui import WebDriverWait
@@ -35,13 +34,19 @@ def simple_sample_mapping(patterns, fruit):
'payloads': payloads
}
-content_script_tree_re = re.compile(r'this.haketilo_pattern_tree = (.*);')
-def extract_tree_data(content_script_text):
- return json.loads(content_script_tree_re.search(content_script_text)[1])
-
-content_script_mapping_re = re.compile(r'this.haketilo_mappings = (.*);')
-def extract_mappings_data(content_script_text):
- return json.loads(content_script_mapping_re.search(content_script_text)[1])
+def get_content_script_values(driver, content_script):
+ """
+ Allow easy extraction of 'this.something = ...' values from generated
+ content script and verify the content script is syntactically correct.
+ """
+ return driver.execute_script(
+ '''
+ function value_holder() {
+ %s;
+ return this;
+ }
+ return value_holder.call({});
+ ''' % content_script)
# Fields that are not relevant for testing are omitted from these mapping
# definitions.
@@ -87,7 +92,7 @@ def test_pqm_tree_building(driver, execute_in_page):
execute_in_page(
'''
const [initial_mappings, initial_blocking] = arguments.slice(0, 2);
- let mappingchange, blockingchange;
+ let mappingchange, blockingchange, settingchange;
haketilodb.track.mapping = function (cb) {
mappingchange = cb;
@@ -99,6 +104,11 @@ def test_pqm_tree_building(driver, execute_in_page):
return [{}, initial_blocking];
}
+ haketilodb.track.settings = function (cb) {
+ settingchange = cb;
+
+ return [{}, [{name: "default_allow", value: true}]];
+ }
let last_script;
let unregister_called = 0;
@@ -110,7 +120,7 @@ def test_pqm_tree_building(driver, execute_in_page):
}
browser = {contentScripts: {register: register_mock}};
- returnval(start());
+ returnval(start("abracadabra"));
''',
sample_mappings[0:2], sample_blocking[0:2])
@@ -125,17 +135,24 @@ def test_pqm_tree_building(driver, execute_in_page):
dict([('~allow', 1),
*[(f'inject-{fruit}', {'identifier': f'{fruit}-{best_pattern}'})
for fruit in ('banana', 'orange')]])
- assert tree == extract_tree_data(content_script)
+ cs_values = get_content_script_values(driver, content_script)
+ assert cs_values['haketilo_secret'] == 'abracadabra'
+ assert cs_values['haketilo_pattern_tree'] == tree
+ assert cs_values['haketilo_default_allow'] == True
assert deregistrations == 0
def condition_all_added(driver):
last_script = execute_in_page('returnval(last_script);')
+ cs_values = get_content_script_values(driver, last_script)
nums = [i for i in range(len(sample_blocking)) if i > 1]
- return (all([('gotmyown%sdoma' % i) in last_script for i in nums]) and
+ return (cs_values['haketilo_default_allow'] == False and
+ all([('gotmyown%sdoma' % i) in last_script for i in nums]) and
all([m['identifier'] in last_script for m in sample_mappings]))
execute_in_page(
'''
+ const new_setting_val = {name: "default_allow", value: false};
+ settingchange({key: "default_allow", new_val: new_setting_val});
for (const mapping of arguments[0])
mappingchange({key: mapping.identifier, new_val: mapping});
for (const blocking of arguments[1])
@@ -163,7 +180,8 @@ def test_pqm_tree_building(driver, execute_in_page):
def condition_all_removed(driver):
content_script = execute_in_page('returnval(last_script);')
- return extract_tree_data(content_script) == {}
+ cs_values = get_content_script_values(driver, content_script)
+ return cs_values['haketilo_pattern_tree'] == {}
execute_in_page(
'''
diff --git a/test/unit/test_popup.py b/test/unit/test_popup.py
index 5319d72..bc53e6c 100644
--- a/test/unit/test_popup.py
+++ b/test/unit/test_popup.py
@@ -62,6 +62,10 @@ mocked_page_infos = {
**unprivileged_page_info,
'mapping': 'm1',
'payload': {'identifier': 'res1'}
+ },
+ 'error': {
+ **unprivileged_page_info,
+ 'error': True
}
}
@@ -143,7 +147,7 @@ def test_popup_display(driver, execute_in_page, page_info_key):
assert by_id['page_url'].text == mocked_page_infos[page_info_key]['url']
assert not by_id['repo_query_container'].is_displayed()
- if 'blocked' in page_info_key or page_info_key == 'mapping':
+ if 'blocked' in page_info_key or page_info_key in ('mapping', 'error'):
assert by_id['scripts_blocked'].text.lower() == 'yes'
elif 'allowed' in page_info_key:
assert by_id['scripts_blocked'].text.lower() == 'no'
@@ -167,6 +171,9 @@ def test_popup_display(driver, execute_in_page, page_info_key):
elif 'default' in page_info_key:
'by default_policy)' in mapping_text
+ if page_info_key == 'error':
+ assert mapping_text == 'None (error occured when determining policy)'
+
@pytest.mark.ext_data(popup_ext_data)
@pytest.mark.usefixtures('webextension')
def test_popup_repo_query(driver, execute_in_page):
diff --git a/test/unit/test_webrequest.py b/test/unit/test_webrequest.py
index 598f43b..fb24b3d 100644
--- a/test/unit/test_webrequest.py
+++ b/test/unit/test_webrequest.py
@@ -30,6 +30,8 @@ def webrequest_js():
''';
// Mock pattern tree.
tree = pqt.make();
+ // Mock default allow.
+ default_allow = {name: "default_allow", value: true};
// Rule to block scripts.
pqt.register(tree, "https://site.with.scripts.block.ed/***",
@@ -40,10 +42,6 @@ def webrequest_js():
pqt.register(tree, "https://site.with.paylo.ad/***",
"somemapping", {identifier: "someresource"});
- // Mock IndexedDB.
- haketilodb.track.settings =
- () => [{}, [{name: "default_allow", value: true}]];
-
// Mock stream_filter.
stream_filter.apply = (details, headers, policy) => headers;