-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_web_ui.py
More file actions
138 lines (110 loc) · 4.44 KB
/
Copy pathtest_web_ui.py
File metadata and controls
138 lines (110 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations
import base64
from html.parser import HTMLParser
import json
from pathlib import Path
import shutil
import subprocess
from secure_vault import SecureVault, __version__
from tools.generate_release_artifacts import read_project_metadata
ROOT = Path(__file__).resolve().parent
INDEX = ROOT / "index.html"
WEB_BRIDGE = ROOT / "tests" / "web_interop.mjs"
class _MarkupInventory(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.elements: list[tuple[str, dict[str, str | None]]] = []
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
self.elements.append((tag, dict(attrs)))
def _run_web_bridge(payload: dict[str, str]) -> str:
node = shutil.which("node")
assert node is not None, "Node.js is required for browser interoperability tests."
result = subprocess.run(
[node, str(WEB_BRIDGE)],
cwd=ROOT,
input=json.dumps(payload),
text=True,
encoding="utf-8",
capture_output=True,
check=False,
)
assert result.returncode == 0, result.stderr
return result.stdout
def _derive_fixture_key(vault: SecureVault, passphrase: str, salt: bytes) -> bytes:
config = vault.CURRENT_KDF_CONFIG
return vault._derive_key(
passphrase,
salt,
config["ops"],
config["mem"],
config["p"],
config["key_len"],
)
def test_browser_handler_preserves_plaintext_whitespace_for_python_decryption() -> None:
vault = SecureVault()
plaintext = " leading spaces\n\tand trailing whitespace \n"
passphrase = "interoperability-test-passphrase"
salt = bytes(range(16))
nonce = bytes(range(32, 44))
key = _derive_fixture_key(vault, passphrase, salt)
blob = _run_web_bridge(
{
"mode": "browser-encrypt",
"plaintext": plaintext,
"passphrase": passphrase,
"salt_b64": base64.b64encode(salt).decode("ascii"),
"nonce_b64": base64.b64encode(nonce).decode("ascii"),
"key_b64": base64.b64encode(key).decode("ascii"),
}
)
assert vault.decrypt(blob, passphrase) == plaintext
def test_python_payload_decrypts_with_browser_crypto_path() -> None:
vault = SecureVault()
plaintext = "Python to browser: café, emoji 🔐, and newlines\nremain intact."
passphrase = "python-to-browser-passphrase"
blob = vault.encrypt(plaintext, passphrase)
packet = json.loads(blob)
salt = base64.b64decode(packet["header"]["salt"], validate=True)
key = _derive_fixture_key(vault, passphrase, salt)
browser_plaintext = _run_web_bridge(
{
"mode": "browser-decrypt",
"blob": blob,
"passphrase": passphrase,
"key_b64": base64.b64encode(key).decode("ascii"),
}
)
assert browser_plaintext == plaintext
def test_runtime_version_is_the_package_metadata_source() -> None:
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
assert 'dynamic = ["version"]' in pyproject
assert '[tool.hatch.version]\npath = "secure_vault.py"' in pyproject
assert read_project_metadata()["version"] == __version__
def test_form_controls_have_programmatic_labels_and_button_types() -> None:
parser = _MarkupInventory()
parser.feed(INDEX.read_text(encoding="utf-8"))
ids = {attrs["id"] for _, attrs in parser.elements if attrs.get("id")}
labels = [attrs for tag, attrs in parser.elements if tag == "label"]
buttons = [attrs for tag, attrs in parser.elements if tag == "button"]
assert labels
assert all(label.get("for") in ids for label in labels)
assert buttons
assert all(button.get("type") == "button" for button in buttons)
def test_tabs_statuses_focus_and_motion_have_accessible_contracts() -> None:
parser = _MarkupInventory()
html = INDEX.read_text(encoding="utf-8")
parser.feed(html)
by_id = {
attrs["id"]: attrs
for _, attrs in parser.elements
if attrs.get("id")
}
assert by_id["tab-encrypt"]["role"] == "tab"
assert by_id["tab-decrypt"]["role"] == "tab"
assert by_id["panel-encrypt"]["role"] == "tabpanel"
assert by_id["panel-decrypt"]["role"] == "tabpanel"
assert by_id["enc-status"]["aria-live"] == "polite"
assert by_id["dec-status"]["aria-live"] == "polite"
assert ":focus-visible" in html
assert "prefers-reduced-motion: reduce" in html
assert "event.key === 'ArrowRight'" in html