aboutsummaryrefslogtreecommitdiff
# SPDX-License-Identifier: CC0-1.0

"""
Haketilo unit tests - serving indexeddb resource script files to content scripts
"""

# 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 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 copy
from uuid import uuid4
from selenium.webdriver.support.ui import WebDriverWait

from ..script_loader import load_script
from .utils import *

"""
How many test resources we're going to have.
"""
count = 15

sample_files_list = [(f'file_{n}_{i}', f'contents {n} {i}')
                       for n in range(count) for i in range(2)]

sample_files = dict(sample_files_list)

sample_files, sample_files_by_sha256 = make_sample_files(sample_files)

def make_sample_resource_with_deps(n):
    resource = make_sample_resource(with_files=False)

    resource['identifier'] = f'res-{n}'
    resource['dependencies'] = [{'identifier': f'res-{m}'}
                                for m in range(max(n - 4, 0), n)]
    resource['scripts'] = [sample_file_ref(f'file_{n}_{i}', sample_files)
                           for i in range(2)]

    return resource

resources = [make_sample_resource_with_deps(n) for n in range(count)]

sample_data = {
    'resource': sample_data_dict(resources),
    'mapping': {},
    'file': {
        'sha256': sample_files_by_sha256
    }
}

def prepare_test_page(initial_indexeddb_data, execute_in_page):
    js = load_script('background/indexeddb_files_server.js',
                     code_to_add='#IMPORT common/broadcast.js')
    execute_in_page(js)

    mock_broadcast(execute_in_page)
    clear_indexeddb(execute_in_page)

    execute_in_page(
        '''
        let registered_listener;
        const new_addListener = cb => registered_listener = cb;

        browser = {runtime: {onMessage: {addListener: new_addListener}}};

        haketilodb.save_items(arguments[0]);

        start();
        ''',
        initial_indexeddb_data)

@pytest.mark.get_page('https://gotmyowndoma.in')
def test_indexeddb_files_server_normal_usage(driver, execute_in_page):
    """
    Test querying resource files (with resource dependency resolution)
    from IndexedDB and serving them in messages to content scripts.
    """
    prepare_test_page(sample_data, execute_in_page)

    # Verify other types of messages are ignored.
    function_returned_value = execute_in_page(
        '''
        returnval(registered_listener(["???"], {},
                                      () => location.reload()));
        ''')
    assert function_returned_value == None

    # Verify single resource's files get properly resolved.
    function_returned_value = execute_in_page(
        '''
        var result_cb, contents_prom = new Promise(cb => result_cb = cb);

        returnval(registered_listener(["indexeddb_files", "res-0"],
                                      {}, result_cb));
        ''')
    assert function_returned_value == True

    assert execute_in_page('returnval(contents_prom);') == \
        {'files': [tuple[1] for tuple in sample_files_list[0:2]]}

    # Verify multiple resources' files get properly resolved.
    function_returned_value = execute_in_page(
        '''
        var result_cb, contents_prom = new Promise(cb => result_cb = cb);

        returnval(registered_listener(["indexeddb_files", arguments[0]],
                                      {}, result_cb));
        ''',
        f'res-{count - 1}')
    assert function_returned_value == True

    assert execute_in_page('returnval(contents_prom);') == \
        {'files': [tuple[1] for tuple in sample_files_list]}

@pytest.mark.get_page('https://gotmyowndoma.in')
@pytest.mark.parametrize('error', [
    'missing',
    'circular',
    'db',
    'other'
])
def test_indexeddb_files_server_errors(driver, execute_in_page, error):
    """
    Test reporting of errors when querying resource files (with resource
    dependency resolution) from IndexedDB and serving them in messages to
    content scripts.
    """
    sample_data_copy = copy.deepcopy(sample_data)

    if error == 'missing':
        del sample_data_copy['resource']['res-3']
    elif error == 'circular':
        res3_defs = sample_data_copy['resource']['res-3'].values()
        next(iter(res3_defs))['dependencies'].append({'identifier': 'res-8'})

    prepare_test_page(sample_data_copy, execute_in_page)

    if error == 'db':
        execute_in_page('haketilodb.idb_get = t => t.onerror("oooops");')
    elif error == 'other':
        execute_in_page('haketilodb.idb_get = () => {throw "oooops"};')

    response = execute_in_page(
        '''
        var result_cb, contents_prom = new Promise(cb => result_cb = cb);

        registered_listener(["indexeddb_files", arguments[0]],
                            {}, result_cb);

        returnval(contents_prom);
        ''',
        f'res-{count - 1}')

    assert response['error']['haketilo_error_type'] == error

    if error == 'missing':
        assert response['error']['id'] == 'res-3'
    elif error == 'circular':
        assert response['error']['id'] in ('res-3', 'res-8')
    elif error not in ('db', 'other'):
        raise Exception('made a typo in test function params?')