diff --git a/tests/test_persimmons_ux.py b/tests/test_persimmons_ux.py new file mode 100644 index 0000000..88d3088 --- /dev/null +++ b/tests/test_persimmons_ux.py @@ -0,0 +1,78 @@ +"""Packaged UX invariants; real computed styles/flows in the browser check.""" +import hashlib +import json +from pathlib import Path +import struct +import unittest +import zipfile + +ROOT = Path(__file__).resolve().parents[1] + + +class UxTests(unittest.TestCase): + def test_packaged_icons_and_pinned_identity(self): + import base64 + with zipfile.ZipFile(ROOT / 'persimmons/web/extension.zip') as archive: + manifest = json.loads(archive.read('persimmons-extension/manifest.json')) + digest = hashlib.sha256(base64.b64decode(manifest['key'])).hexdigest()[:32] + self.assertEqual(''.join(chr(ord('a') + int(c, 16)) for c in digest), + 'kiolgjoohhgdohcafgelhikpgkbmckbg') + self.assertEqual( + manifest['version'], + json.loads((ROOT / 'persimmons-extension/manifest.json').read_text())['version'], + ) + for size in (16, 32, 48, 128): + name = manifest['icons'][str(size)] + self.assertEqual(manifest['action']['default_icon'][str(size)], name) + raw = archive.read('persimmons-extension/' + name) + self.assertEqual(raw[:8], b'\x89PNG\r\n\x1a\n') + self.assertEqual(struct.unpack('>II', raw[16:24]), (size, size)) + self.assertEqual(raw, (ROOT / 'persimmons-extension' / name).read_bytes()) + self.assertFalse(any('/tests/' in name for name in archive.namelist())) + self.assertEqual(manifest['externally_connectable'], {'matches': ['https://persimmons.app/*']}) + self.assertNotIn('content_scripts', manifest) + self.assertEqual(manifest['permissions'], ['activeTab', 'scripting', 'storage']) + popup = archive.read('persimmons-extension/popup.html').decode() + self.assertIn('Save autofilled login', popup) + self.assertIn('Lock vault', popup) + self.assertIn('Settings', popup) + self.assertNotIn('put plainly', html) + self.assertNotIn('Persimmons are', html) + self.assertNotIn('without paperwork', html) + self.assertNotIn('
', html) + hosted = (ROOT / 'persimmons/web/index.html').read_text() + self.assertIn('Master vault', hosted) + self.assertIn('id="namedVaults"', hosted) + self.assertIn('+ New vault', hosted) + self.assertIn('Passwords, PATs & plenty of other useful P’s.', hosted) + self.assertNotIn('My vault', hosted) + self.assertNotIn('id="refreshAll"', hosted) + self.assertIn('id="addCredential"', hosted) + self.assertNotIn('Your Persimmons', hosted) + + def test_password_hints_preserve_masking_and_validation(self): + from html.parser import HTMLParser + class Parser(HTMLParser): + def __init__(self): + super().__init__() + self.nodes = {} + def handle_starttag(self, tag, attrs): + attrs = dict(attrs) + if 'id' in attrs: + self.nodes[attrs['id']] = attrs + page = Parser() + page.feed((ROOT / 'persimmons/web/signin.html').read_text()) + for name, value in [('signin', 'on'), ('name', 'username'), ('password', 'current-password')]: + self.assertEqual(page.nodes[name]['autocomplete'], value) + self.assertEqual(page.nodes['password']['type'], 'password') + self.assertEqual(page.nodes['password']['minlength'], '12') + self.assertIn('required', page.nodes['password'])