Project

General

Profile

Download (18.1 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / test / haketilo_test / unit / test_patterns_query_tree.py @ c8294257

1
# SPDX-License-Identifier: CC0-1.0
2

    
3
"""
4
Haketilo unit tests - URL patterns
5
"""
6

    
7
# This file is part of Haketilo
8
#
9
# Copyright (C) 2021, 2022 Wojtek Kosior <koszko@koszko.org>
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the CC0 1.0 Universal License as published by
13
# the Creative Commons Corporation.
14
#
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
# CC0 1.0 Universal License for more details.
19

    
20
import pytest
21

    
22
from ..script_loader import load_script
23

    
24
@pytest.mark.get_page('https://gotmyowndoma.in')
25
def test_modify_branch(execute_in_page):
26
    """
27
    patterns_query_tree.js contains Pattern Tree data structure that allows
28
    arrays of string labels to be mapped to items.
29
    Verify operations modifying a single branch of such tree work properly.
30
    """
31
    execute_in_page(load_script('common/patterns_query_tree.js'))
32
    execute_in_page(
33
        '''
34
        let items_added;
35
        let items_removed;
36

    
37
        function _item_adder(item, array)
38
        {
39
            items_added++;
40
            return [...(array || []), item];
41
        }
42

    
43
        function item_adder(item)
44
        {
45
            items_added = 0;
46
            return array => _item_adder(item, array);
47
        }
48

    
49
        function _item_remover(array)
50
        {
51
            if (array !== null) {
52
                items_removed++;
53
                array.pop();
54
            }
55
            return (array && array.length > 0) ? array : null;
56
        }
57

    
58
        function item_remover()
59
        {
60
            items_removed = 0;
61
            return _item_remover;
62
        }''')
63

    
64
    # Let's construct some tree branch while checking that each addition gives
65
    # the right result.
66
    branch = execute_in_page(
67
        '''{
68
        const branch = empty_node();
69
        modify_sequence(branch, ['com', 'example'], item_adder('some_item'));
70
        returnval(branch);
71
        }''')
72
    assert branch == {
73
        'c': {
74
            'com': {
75
                'c': {
76
                    'example': {
77
                        'l': ['some_item']
78
                    }
79
                }
80
            }
81
        }
82
    }
83

    
84
    branch, items_added = execute_in_page(
85
        '''{
86
        const branch = arguments[0];
87
        modify_sequence(branch, ['com', 'example'], item_adder('other_item'));
88
        returnval([branch, items_added]);
89
        }''', branch)
90
    assert items_added == 1
91
    assert branch['c']['com']['c']['example']['l'] \
92
        == ['some_item', 'other_item']
93

    
94
    for i in range(3):
95
        for expected_array in [['third_item'], ['third_item', '4th_item']]:
96
            wildcard = '*' * (i + 1)
97
            branch, items_added = execute_in_page(
98
                '''{
99
                const branch = arguments[0];
100
                modify_sequence(branch, ['com', 'sample', arguments[1]],
101
                                item_adder(arguments[2]));
102
                returnval([branch, items_added]);
103
                }''',
104
                branch, wildcard, expected_array[-1])
105
            assert items_added == 2
106
            sample = branch['c']['com']['c']['sample']
107
            assert sample[wildcard] == expected_array
108
            assert sample['c'][wildcard]['l'] == expected_array
109

    
110
    branch, items_added = execute_in_page(
111
        '''{
112
        const branch = arguments[0];
113
        modify_sequence(branch, ['org', 'koszko', '***', '123'],
114
                        item_adder('5th_item'));
115
        returnval([branch, items_added]);
116
        }''',
117
        branch)
118
    assert items_added == 1
119
    assert branch['c']['org']['c']['koszko']['c']['***']['c']['123']['l'] \
120
        == ['5th_item']
121

    
122
    # Let's verify that removing a nonexistent element doesn't modify the tree.
123
    branch2, items_removed = execute_in_page(
124
        '''{
125
        const branch = arguments[0];
126
        modify_sequence(branch, ['com', 'not', 'registered', '*'],
127
                        item_remover());
128
        returnval([branch, items_removed]);
129
        }''',
130
        branch)
131
    assert branch == branch2
132
    assert items_removed == 0
133

    
134
    # Let's remove all elements in the tree branch while checking that each
135
    # removal gives the right result.
136
    branch, items_removed = execute_in_page(
137
        '''{
138
        const branch = arguments[0];
139
        modify_sequence(branch, ['org', 'koszko', '***', '123'],
140
                        item_remover());
141
        returnval([branch, items_removed]);
142
        }''',
143
        branch)
144
    assert items_removed == 1
145
    assert 'org' not in branch['c']
146

    
147
    for i in range(3):
148
        for expected_array in [['third_item'], None]:
149
            wildcard = '*' * (i + 1)
150
            branch, items_removed = execute_in_page(
151
                '''{
152
                const branch = arguments[0];
153
                modify_sequence(branch, ['com', 'sample', arguments[1]],
154
                                item_remover());
155
                returnval([branch, items_removed]);
156
                }''',
157
                branch, wildcard)
158
            assert items_removed == 2
159
            if i == 2 and expected_array == []:
160
                break
161
            sample = branch['c']['com']['c'].get('sample', {})
162
            assert sample.get(wildcard) == expected_array
163
            assert sample.get('c', {}).get(wildcard, {}).get('l') \
164
                == expected_array
165

    
166
    for i in range(2):
167
        branch, items_removed = execute_in_page(
168
            '''{
169
            const branch = arguments[0];
170
            modify_sequence(branch, ['com', 'example'], item_remover());
171
            returnval([branch, items_removed]);
172
            }''',
173
            branch)
174
        assert items_removed == 1
175
        if i == 0:
176
            assert branch['c']['com']['c']['example']['l'] == ['some_item']
177

    
178
    assert branch == {}
179

    
180
@pytest.mark.get_page('https://gotmyowndoma.in')
181
def test_search_branch(execute_in_page):
182
    """
183
    patterns_query_tree.js contains Pattern Tree data structure that allows
184
    arrays of string labels to be mapped to items.
185
    Verify searching a single branch of such tree work properly.
186
    """
187
    execute_in_page(load_script('common/patterns_query_tree.js'))
188
    execute_in_page(
189
        '''
190
        const item_adder = item => (array => [...(array || []), item]);
191
        ''')
192

    
193
    # Let's construct some tree branch to test on.
194
    execute_in_page(
195
        '''
196
        var branch = empty_node();
197

    
198
        for (const [item, sequence] of [
199
            ['(root)', []],
200
            ['***',    ['***']],
201
            ['**',     ['**']],
202
            ['*',      ['*']],
203

    
204
            ['a',      ['a']],
205
            ['A',      ['a']],
206
            ['b',      ['b']],
207

    
208
            ['a/***',  ['a', '***']],
209
            ['A/***',  ['a', '***']],
210
            ['a/**',   ['a', '**']],
211
            ['A/**',   ['a', '**']],
212
            ['a/*',    ['a', '*']],
213
            ['A/*',    ['a', '*']],
214
            ['a/sth',  ['a', 'sth']],
215
            ['A/sth',  ['a', 'sth']],
216

    
217
            ['b/***',  ['b', '***']],
218
            ['b/**',   ['b', '**']],
219
            ['b/*',    ['b', '*']],
220
            ['b/sth',  ['b', 'sth']],
221
        ])
222
            modify_sequence(branch, sequence, item_adder(item));
223
        ''')
224

    
225
    # Let's make the actual searches on our testing branch.
226
    for sequence, expected in [
227
            ([],      [{'(root)'},                            {'***'}]),
228
            (['a'],   [{'a', 'A'}, {'a/***', 'A/***'}, {'*'}, {'***'}]),
229
            (['b'],   [{'b'},      {'b/***'},          {'*'}, {'***'}]),
230
            (['c'],   [                                {'*'}, {'***'}]),
231
            (['***'], [{'***'},                        {'*'}         ]),
232
            (['**'],  [{'**'},                         {'*'}, {'***'}]),
233
            (['**'],  [{'**'},                         {'*'}, {'***'}]),
234
            (['*'],   [{'*'},                                 {'***'}]),
235

    
236
            (['a', 'sth'], [{'a/sth', 'A/sth'}, {'a/*', 'A/*'}, {'a/***', 'A/***'}, {'**'}, {'***'}]),
237
            (['b', 'sth'], [{'b/sth'},          {'b/*'},        {'b/***'},          {'**'}, {'***'}]),
238
            (['a', 'hts'], [                    {'a/*', 'A/*'}, {'a/***', 'A/***'}, {'**'}, {'***'}]),
239
            (['b', 'hts'], [                    {'b/*'},        {'b/***'},          {'**'}, {'***'}]),
240
            (['a', '***'], [{'a/***', 'A/***'}, {'a/*', 'A/*'},                     {'**'}, {'***'}]),
241
            (['b', '***'], [{'b/***'},          {'b/*'},                            {'**'}, {'***'}]),
242
            (['a', '**'],  [{'a/**', 'A/**'},   {'a/*', 'A/*'}, {'a/***', 'A/***'}, {'**'}, {'***'}]),
243
            (['b', '**'],  [{'b/**'},           {'b/*'},        {'b/***'},          {'**'}, {'***'}]),
244
            (['a', '*'],   [{'a/*', 'A/*'},                     {'a/***', 'A/***'}, {'**'}, {'***'}]),
245
            (['b', '*'],   [{'b/*'},                            {'b/***'},          {'**'}, {'***'}]),
246

    
247
            (['a', 'c', 'd'], [{'a/**', 'A/**'}, {'a/***', 'A/***'}, {'**'}, {'***'}]),
248
            (['b', 'c', 'd'], [{'b/**'},         {'b/***'},          {'**'}, {'***'}])
249
    ]:
250
        result = execute_in_page(
251
            '''
252
            returnval([...search_sequence(branch, arguments[0])]);
253
            ''',
254
            sequence)
255

    
256
        try:
257
            assert len(result) == len(expected)
258

    
259
            for expected_set, result_array in zip(expected, result):
260
                assert len(expected_set) == len(result_array)
261
                assert expected_set      == set(result_array)
262
        except Exception as e:
263
            import sys
264
            print('sequence:', sequence, '\nexpected:', expected,
265
                  '\nresult:', result, file=sys.stderr)
266
            raise e from None
267

    
268
@pytest.mark.get_page('https://gotmyowndoma.in')
269
def test_pattern_tree(execute_in_page):
270
    """
271
    patterns_query_tree.js contains Pattern Tree data structure that allows
272
    arrays of string labels to be mapped to items.
273
    Verify operations on entire such tree work properly.
274
    """
275
    execute_in_page(load_script('common/patterns_query_tree.js'))
276

    
277
    # Perform tests with all possible patterns for a simple URL.
278
    url = 'https://example.com'
279
    patterns = [
280
        'https://example.com',
281
        'https://example.com/***',
282
        'https://***.example.com',
283
        'https://***.example.com/***'
284
    ]
285
    bad_patterns = [
286
        'http://example.com',
287
        'https://a.example.com',
288
        'https://*.example.com',
289
        'https://**.example.com',
290
        'https://example.com/a',
291
        'https://example.com/*',
292
        'https://example.com/**',
293
    ]
294

    
295
    expected = [{'key': p} for p in patterns]
296

    
297
    tree, result = execute_in_page(
298
        '''{
299
        const tree = pattern_tree_make();
300
        for (const pattern of arguments[0].concat(arguments[1])) {
301
            pattern_tree_register(tree, pattern,       'key', pattern);
302
            pattern_tree_register(tree, pattern + '/', 'key', pattern + '/');
303
        }
304
        returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
305
        }''',
306
        patterns, bad_patterns, url)
307
    assert expected == result
308

    
309
    # Also verify that deregistering half of the good patterns works correctly.
310
    patterns_removed = [pattern for i, pattern in enumerate(patterns) if i % 2]
311
    patterns = [pattern for i, pattern in enumerate(patterns) if not (i % 2)]
312
    expected = [{'key': p} for p in patterns]
313
    tree, result = execute_in_page(
314
        '''{
315
        const tree = arguments[0];
316
        for (const pattern of arguments[1]) {
317
            pattern_tree_deregister(tree, pattern,       'key');
318
            pattern_tree_deregister(tree, pattern + '/', 'key');
319
        }
320
        returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
321
        }''',
322
        tree, patterns_removed, url)
323
    assert expected == result
324

    
325
    # Also verify that deregistering all the patterns works correctly.
326
    tree = execute_in_page(
327
        '''{
328
        const tree = arguments[0];
329
        for (const pattern of arguments[1].concat(arguments[2])) {
330
            pattern_tree_deregister(tree, pattern,       'key');
331
            pattern_tree_deregister(tree, pattern + '/', 'key');
332
        }
333
        returnval(tree);
334
        }''',
335
        tree, patterns, bad_patterns)
336
    assert tree == {}
337

    
338
    # Perform tests with all possible patterns for a complex URL.
339
    url = 'http://settings.query.example.com/google/tries/destroy/adblockers//'
340
    patterns = [
341
        'http://settings.query.example.com/google/tries/destroy/adblockers',
342
        'http://settings.query.example.com/google/tries/destroy/adblockers/***',
343
        'http://settings.query.example.com/google/tries/destroy/*',
344
        'http://settings.query.example.com/google/tries/destroy/***',
345
        'http://settings.query.example.com/google/tries/**',
346
        'http://settings.query.example.com/google/tries/***',
347
        'http://settings.query.example.com/google/**',
348
        'http://settings.query.example.com/google/***',
349
        'http://settings.query.example.com/**',
350
        'http://settings.query.example.com/***',
351

    
352
        'http://***.settings.query.example.com/google/tries/destroy/adblockers',
353
        'http://***.settings.query.example.com/google/tries/destroy/adblockers/***',
354
        'http://***.settings.query.example.com/google/tries/destroy/*',
355
        'http://***.settings.query.example.com/google/tries/destroy/***',
356
        'http://***.settings.query.example.com/google/tries/**',
357
        'http://***.settings.query.example.com/google/tries/***',
358
        'http://***.settings.query.example.com/google/**',
359
        'http://***.settings.query.example.com/google/***',
360
        'http://***.settings.query.example.com/**',
361
        'http://***.settings.query.example.com/***',
362
        'http://*.query.example.com/google/tries/destroy/adblockers',
363
        'http://*.query.example.com/google/tries/destroy/adblockers/***',
364
        'http://*.query.example.com/google/tries/destroy/*',
365
        'http://*.query.example.com/google/tries/destroy/***',
366
        'http://*.query.example.com/google/tries/**',
367
        'http://*.query.example.com/google/tries/***',
368
        'http://*.query.example.com/google/**',
369
        'http://*.query.example.com/google/***',
370
        'http://*.query.example.com/**',
371
        'http://*.query.example.com/***',
372
        'http://***.query.example.com/google/tries/destroy/adblockers',
373
        'http://***.query.example.com/google/tries/destroy/adblockers/***',
374
        'http://***.query.example.com/google/tries/destroy/*',
375
        'http://***.query.example.com/google/tries/destroy/***',
376
        'http://***.query.example.com/google/tries/**',
377
        'http://***.query.example.com/google/tries/***',
378
        'http://***.query.example.com/google/**',
379
        'http://***.query.example.com/google/***',
380
        'http://***.query.example.com/**',
381
        'http://***.query.example.com/***',
382
        'http://**.example.com/google/tries/destroy/adblockers',
383
        'http://**.example.com/google/tries/destroy/adblockers/***',
384
        'http://**.example.com/google/tries/destroy/*',
385
        'http://**.example.com/google/tries/destroy/***',
386
        'http://**.example.com/google/tries/**',
387
        'http://**.example.com/google/tries/***',
388
        'http://**.example.com/google/**',
389
        'http://**.example.com/google/***',
390
        'http://**.example.com/**',
391
        'http://**.example.com/***',
392
        'http://***.example.com/google/tries/destroy/adblockers',
393
        'http://***.example.com/google/tries/destroy/adblockers/***',
394
        'http://***.example.com/google/tries/destroy/*',
395
        'http://***.example.com/google/tries/destroy/***',
396
        'http://***.example.com/google/tries/**',
397
        'http://***.example.com/google/tries/***',
398
        'http://***.example.com/google/**',
399
        'http://***.example.com/google/***',
400
        'http://***.example.com/**',
401
        'http://***.example.com/***'
402
    ]
403
    bad_patterns = [
404
        'https://settings.query.example.com/google/tries/destroy/adblockers',
405
        'http://settings.query.example.com/google/tries/destroy/adblockers/a',
406
        'http://settings.query.example.com/google/tries/destroy/adblockers/*',
407
        'http://settings.query.example.com/google/tries/destroy/adblockers/**',
408
        'http://settings.query.example.com/google/tries/destroy/a',
409
        'http://settings.query.example.com/google/tries/destroy/**',
410
        'http://settings.query.example.com/google/tries/*',
411
        'http://a.settings.query.example.com/google/tries/destroy/adblockers',
412
        'http://*.settings.query.example.com/google/tries/destroy/adblockers',
413
        'http://**.settings.query.example.com/google/tries/destroy/adblockers',
414
        'http://a.query.example.com/google/tries/destroy/adblockers',
415
        'http://**.query.example.com/google/tries/destroy/adblockers',
416
        'http://*.example.com/google/tries/destroy/adblockers'
417
    ]
418

    
419
    expected = [{'key': p + s} for p in patterns for s in ['/', '']]
420

    
421
    tree, result = execute_in_page(
422
        '''{
423
        const tree = pattern_tree_make();
424
        for (const pattern of arguments[0].concat(arguments[1])) {
425
            pattern_tree_register(tree, pattern,       'key', pattern);
426
            pattern_tree_register(tree, pattern + '/', 'key', pattern + '/');
427
        }
428
        returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
429
        }''',
430
        patterns, bad_patterns, url)
431
    assert expected == result
432

    
433
    # Also verify that deregistering all patterns with trailing slash works
434
    # correctly.
435
    expected = [{'key': p} for p in patterns]
436
    tree, result = execute_in_page(
437
        '''{
438
        const tree = arguments[0];
439
        for (const pattern of arguments[1])
440
            pattern_tree_deregister(tree, pattern + '/', 'key');
441
        returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
442
        }''',
443
        tree, patterns, url)
444
    assert expected == result
445

    
446
    # Also verify that deregistering all the patterns works correctly.
447
    tree = execute_in_page(
448
        '''{
449
        const tree = arguments[0];
450
        for (const pattern of arguments[1])
451
            pattern_tree_deregister(tree, pattern,       'key');
452
        for (const pattern of arguments[2]) {
453
            pattern_tree_deregister(tree, pattern,       'key');
454
            pattern_tree_deregister(tree, pattern + '/', 'key');
455
        }
456
        returnval(tree);
457
        }''',
458
        tree, patterns, bad_patterns)
459
    assert tree == {}
(15-15/25)