aboutsummaryrefslogtreecommitdiff
path: root/test/haketilo_test/unit/test_content.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/haketilo_test/unit/test_content.py')
-rw-r--r--test/haketilo_test/unit/test_content.py190
1 files changed, 190 insertions, 0 deletions
diff --git a/test/haketilo_test/unit/test_content.py b/test/haketilo_test/unit/test_content.py
new file mode 100644
index 0000000..8220160
--- /dev/null
+++ b/test/haketilo_test/unit/test_content.py
@@ -0,0 +1,190 @@
+# 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_default_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 */
+
+ function mock_decide_policy() {
+ nonce = "12345";
+ return {
+ allow: false,
+ mapping: "what-is-programmers-favorite-drinking-place",
+ payload: {identifier: "foo-bar"},
+ nonce,
+ csp: "prefetch-src 'none'; script-src-attr 'none'; script-src 'nonce-12345'; script-src-elem 'nonce-12345';"
+ };
+ }
+
+ async function mock_payload_error([type, res_id]) {
+ if (type === "indexeddb_files")
+ return {error: {haketilo_error_type: "missing", id: res_id}};
+ }
+
+ async function mock_payload_ok([type, res_id]) {
+ if (type === "indexeddb_files")
+ return {files: [1, 2].map(n => `window.hak_injected_${n} = ${n};`)};
+ }
+
+ if (/payload_error/.test(document.URL)) {
+ browser.runtime.sendMessage = mock_payload_error;
+ decide_policy = mock_decide_policy;
+ } else if (/payload_ok/.test(document.URL)) {
+ browser.runtime.sendMessage = mock_payload_ok;
+ decide_policy = mock_decide_policy;
+ }
+ /* Otherwise, script blocking policy without payload to inject is used. */
+
+ 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, 10));
+
+ /* Mock a good request. */
+ const set_good = val => data_set("good_request_result", val);
+ data_set("good_request_returned",
+ !!listener_cb(["page_info"], {}, val => set_good(val)));
+
+ /* Mock a bad request. */
+ const set_bad = val => data_set("bad_request_result", val);
+ data_set("bad_request_returned",
+ !!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('target1', ['dynamic_before'])#, 'dynamic_after'])
+@pytest.mark.parametrize('target2', [
+ 'scripts_blocked',
+ 'payload_error',
+ 'payload_ok'
+])
+def test_content_unprivileged_page(driver, execute_in_page, target1, target2):
+ """
+ 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#{target1}-{target2}')
+
+ def get_data(driver):
+ data = driver.execute_script('return window.data_to_verify;')
+ return data if 'good_request_result' in data else False
+
+ data = json.loads(WebDriverWait(driver, 10).until(get_data))
+
+ assert 'gotmyowndoma.in' in data['good_request_result']['url']
+ assert 'bad_request_result' not in data
+
+ assert data['good_request_returned'] == True
+ assert data['bad_request_returned'] == False
+
+ assert data['cacher_started'] == True
+
+ for obj in (data['good_request_result'], data['enforcing']):
+ assert obj['allow'] == False
+
+ assert 'error' not in data['enforcing']
+
+ if target2.startswith('payload'):
+ for obj in (data['good_request_result'], data['enforcing']):
+ assert obj['payload']['identifier'] == 'foo-bar'
+ assert 'mapping' in obj
+ else:
+ assert 'payload' not in data['enforcing']
+ assert 'mapping' not in data['enforcing']
+
+ assert data['script_run_without_errors'] == True
+
+ def vars_made_by_payload(driver):
+ vars_values = driver.execute_script(
+ 'return [1, 2].map(n => window[`hak_injected_${n}`]);'
+ )
+ if vars_values != [None, None]:
+ return vars_values
+
+ if target2 == 'payload_error':
+ assert data['good_request_result']['error'] == {
+ 'haketilo_error_type': 'missing',
+ 'id': 'foo-bar'
+ }
+ elif target2 == 'payload_ok':
+ vars_values = WebDriverWait(driver, 10).until(vars_made_by_payload)
+ assert vars_values == [1, 2]
+
+@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}