1
|
# SPDX-License-Identifier: CC0-1.0
|
2
|
|
3
|
"""
|
4
|
Haketilo unit tests - using a form to create simple site payload
|
5
|
"""
|
6
|
|
7
|
# This file is part of Haketilo
|
8
|
#
|
9
|
# Copyright (C) 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
|
import re
|
22
|
from hashlib import sha256
|
23
|
|
24
|
from selenium.webdriver.support.ui import WebDriverWait
|
25
|
|
26
|
from ..extension_crafting import ExtraHTML
|
27
|
from ..script_loader import load_script
|
28
|
from .utils import *
|
29
|
|
30
|
uuidv4_re = re.compile(
|
31
|
r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$',
|
32
|
re.IGNORECASE
|
33
|
)
|
34
|
|
35
|
sample_patterns = '''
|
36
|
http://example.com/***
|
37
|
|
38
|
https://*.example.org/**'''
|
39
|
|
40
|
sample_form_data = {
|
41
|
'identifier': 'someid',
|
42
|
'long_name': 'Some Name',
|
43
|
'description': 'blah blah blah',
|
44
|
'patterns': sample_patterns,
|
45
|
'script': sample_files['hello.js']['contents']
|
46
|
}
|
47
|
|
48
|
def fill_form_with_sample_data(execute_in_page, sample_data_override={},
|
49
|
form_ctx='form_ctx'):
|
50
|
form_data = sample_form_data.copy()
|
51
|
form_data.update(sample_data_override)
|
52
|
execute_in_page(
|
53
|
f'''
|
54
|
for (const [key, value] of Object.entries(arguments[0]))
|
55
|
{form_ctx}[key].value = value;
|
56
|
''',
|
57
|
form_data)
|
58
|
return form_data
|
59
|
|
60
|
cleared_form_inputs = {
|
61
|
'identifier': '',
|
62
|
'long_name': '',
|
63
|
'description': '',
|
64
|
'patterns': 'https://example.com/***',
|
65
|
'script': 'console.log("Hello, World!");'
|
66
|
}
|
67
|
def assert_form_contents(execute_in_page, inputs=cleared_form_inputs):
|
68
|
inputs_keys = [*inputs.keys()]
|
69
|
values = execute_in_page(
|
70
|
'returnval(arguments[0].map(i => form_ctx[i].value));',
|
71
|
inputs_keys
|
72
|
)
|
73
|
for key, value in zip(inputs_keys, values):
|
74
|
assert inputs[key] == value
|
75
|
|
76
|
@pytest.mark.ext_data({
|
77
|
'background_script': broker_js,
|
78
|
'extra_html': ExtraHTML('html/payload_create.html', {}),
|
79
|
'navigate_to': 'html/payload_create.html'
|
80
|
})
|
81
|
@pytest.mark.usefixtures('webextension')
|
82
|
def test_payload_create_normal_usage(driver, execute_in_page):
|
83
|
"""
|
84
|
A test case of normal usage of simple payload creation form.
|
85
|
"""
|
86
|
execute_in_page(load_script('html/payload_create.js'))
|
87
|
|
88
|
create_but, form_container, dialog_container = execute_in_page(
|
89
|
'''
|
90
|
const form_ctx = payload_create_form();
|
91
|
document.body.append(form_ctx.main_div);
|
92
|
returnval([form_ctx.create_but, form_ctx.form_container,
|
93
|
form_ctx.dialog_container]);
|
94
|
''')
|
95
|
|
96
|
assert patterns_doc_url == \
|
97
|
driver.find_element_by_link_text('URL patterns').get_attribute('href')
|
98
|
|
99
|
assert form_container.is_displayed()
|
100
|
assert not dialog_container.is_displayed()
|
101
|
|
102
|
assert_form_contents(execute_in_page)
|
103
|
|
104
|
form_data = fill_form_with_sample_data(execute_in_page)
|
105
|
|
106
|
create_but.click()
|
107
|
|
108
|
assert not form_container.is_displayed()
|
109
|
assert dialog_container.is_displayed()
|
110
|
|
111
|
def success_reported(driver):
|
112
|
return 'Successfully saved payload' in dialog_container.text
|
113
|
|
114
|
WebDriverWait(driver, 10).until(success_reported)
|
115
|
execute_in_page('form_ctx.dialog_ctx.ok_but.click();')
|
116
|
|
117
|
assert form_container.is_displayed()
|
118
|
assert not dialog_container.is_displayed()
|
119
|
|
120
|
def assert_db_contents():
|
121
|
db_contents = get_db_contents(execute_in_page)
|
122
|
|
123
|
assert uuidv4_re.match(db_contents['resource'][0]['uuid'])
|
124
|
|
125
|
localid = f'local-{form_data["identifier"]}'
|
126
|
long_name = form_data['long_name'] or form_data['identifier']
|
127
|
payloads = dict([(pat, {'identifier': localid})
|
128
|
for pat in form_data['patterns'].split('\n') if pat])
|
129
|
|
130
|
assert db_contents['resource'] == [{
|
131
|
'source_name': localid,
|
132
|
'source_copyright': [],
|
133
|
'type': 'resource',
|
134
|
'identifier': localid,
|
135
|
'uuid': db_contents['resource'][0]['uuid'],
|
136
|
'version': [1],
|
137
|
'description': form_data['description'],
|
138
|
'dependencies': [],
|
139
|
'long_name': long_name,
|
140
|
'scripts': [{
|
141
|
'file': 'payload.js',
|
142
|
'sha256': sha256(form_data['script'].encode()).digest().hex()
|
143
|
}]
|
144
|
}]
|
145
|
|
146
|
assert uuidv4_re.match(db_contents['mapping'][0]['uuid'])
|
147
|
assert db_contents['mapping'] == [{
|
148
|
'source_name': localid,
|
149
|
'source_copyright': [],
|
150
|
'type': 'mapping',
|
151
|
'identifier': localid,
|
152
|
'uuid': db_contents['mapping'][0]['uuid'],
|
153
|
'version': [1],
|
154
|
'description': form_data['description'],
|
155
|
'long_name': long_name,
|
156
|
'payloads': payloads
|
157
|
}]
|
158
|
|
159
|
assert_db_contents()
|
160
|
|
161
|
form_data = fill_form_with_sample_data(execute_in_page, {
|
162
|
'long_name': '',
|
163
|
'description': 'bam bam bam',
|
164
|
'patterns': 'https://new.example.com/***',
|
165
|
'script': sample_files['bye.js']['contents']
|
166
|
})
|
167
|
|
168
|
create_but.click()
|
169
|
|
170
|
for type in ('Resource', 'Mapping'):
|
171
|
def override_asked(driver):
|
172
|
return f"{type} 'local-someid' already exists. Override?" \
|
173
|
in dialog_container.text
|
174
|
WebDriverWait(driver, 10).until(override_asked)
|
175
|
execute_in_page('form_ctx.dialog_ctx.yes_but.click();')
|
176
|
|
177
|
assert_db_contents()
|
178
|
|
179
|
@pytest.mark.ext_data({
|
180
|
'background_script': broker_js,
|
181
|
'extra_html': ExtraHTML('html/payload_create.html', {}),
|
182
|
'navigate_to': 'html/payload_create.html'
|
183
|
})
|
184
|
@pytest.mark.usefixtures('webextension')
|
185
|
def test_payload_create_errors(driver, execute_in_page):
|
186
|
"""
|
187
|
A test case of various error the simple payload form might show.
|
188
|
"""
|
189
|
execute_in_page(load_script('html/payload_create.js'))
|
190
|
|
191
|
create_but, dialog_container = execute_in_page(
|
192
|
'''
|
193
|
const form_ctx = payload_create_form();
|
194
|
document.body.append(form_ctx.main_div);
|
195
|
returnval([form_ctx.create_but, form_ctx.dialog_container]);
|
196
|
''')
|
197
|
|
198
|
for data_override, expected_msg in [
|
199
|
({'identifier': ''}, "The 'identifier' field is required!"),
|
200
|
({'identifier': ':('}, 'Identifier may only contain '),
|
201
|
({'script': ''}, "The 'script' field is required!"),
|
202
|
({'patterns': ''}, "The 'URL patterns' field is required!"),
|
203
|
({'patterns': ':d'}, "':d' is not a valid URL pattern. See here for more details."),
|
204
|
({'patterns': '\n'.join(['http://example.com'] * 2)},
|
205
|
"Pattern 'http://example.com' specified multiple times!")
|
206
|
]:
|
207
|
# Attempt creating the payload
|
208
|
form_data = fill_form_with_sample_data(execute_in_page, data_override)
|
209
|
create_but.click()
|
210
|
# Verify the error message
|
211
|
assert expected_msg in dialog_container.text
|
212
|
|
213
|
# Verify patterns documentation <a> link.
|
214
|
if expected_msg == {'patterns': ':d'}:
|
215
|
doc_link_elem = driver.find_element_by_link_text('here')
|
216
|
assert doc_link.get_attribute('href') == patterns_doc_url
|
217
|
|
218
|
# Verify the form was NOT cleared upon failed saving.
|
219
|
execute_in_page('form_ctx.dialog_ctx.ok_but.click();')
|
220
|
assert_form_contents(execute_in_page, form_data)
|
221
|
|
222
|
# Add a sample item and attempt overriding it.
|
223
|
fill_form_with_sample_data(execute_in_page)
|
224
|
create_but.click()
|
225
|
WebDriverWait(driver, 10).until(lambda _: 'Succes' in dialog_container.text)
|
226
|
execute_in_page('form_ctx.dialog_ctx.ok_but.click();')
|
227
|
|
228
|
# Verify that denying override leads to saving failure.
|
229
|
form_data = fill_form_with_sample_data(execute_in_page)
|
230
|
create_but.click()
|
231
|
WebDriverWait(driver, 10).until(lambda _: 'Overri' in dialog_container.text)
|
232
|
execute_in_page('form_ctx.dialog_ctx.no_but.click();')
|
233
|
assert 'Failed to save payload :(' in dialog_container.text
|
234
|
execute_in_page('form_ctx.dialog_ctx.ok_but.click();')
|
235
|
assert_form_contents(execute_in_page, form_data)
|
236
|
|
237
|
# Verify that IndexedDB errors get caught and reported as saving failures.
|
238
|
execute_in_page('haketilodb.get = async () => {throw "someerror";}')
|
239
|
form_data = fill_form_with_sample_data(execute_in_page, {'identifier': 'o'})
|
240
|
create_but.click()
|
241
|
WebDriverWait(driver, 10).until(lambda _: 'Failed' in dialog_container.text)
|
242
|
execute_in_page('form_ctx.dialog_ctx.ok_but.click();')
|
243
|
assert_form_contents(execute_in_page, form_data)
|
244
|
|
245
|
# Verify that the loading message gets shown during IndexedDB operations.
|
246
|
execute_in_page('haketilodb.get = () => new Promise(cb => null);')
|
247
|
create_but.click()
|
248
|
assert 'Saving payload...' in dialog_container.text
|