2026-10-04 02:58:06 +00:00
|
|
|
|
"""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
|
2026-10-04 08:05:18 +00:00
|
|
|
|
with zipfile.ZipFile(ROOT / 'assets/extension.zip') as archive:
|
2026-10-04 02:58:06 +00:00
|
|
|
|
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'],
|
2026-10-04 08:05:18 +00:00
|
|
|
|
json.loads((ROOT / 'extension/manifest.json').read_text())['version'],
|
2026-10-04 02:58:06 +00:00
|
|
|
|
)
|
|
|
|
|
|
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))
|
2026-10-04 08:05:18 +00:00
|
|
|
|
self.assertEqual(raw, (ROOT / 'extension' / name).read_bytes())
|
2026-10-04 02:58:06 +00:00
|
|
|
|
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)
|
2026-10-04 08:05:18 +00:00
|
|
|
|
self.assertNotIn('optional_host_permissions', manifest)
|
|
|
|
|
|
self.assertEqual(manifest['host_permissions'], ['https://teahub.xyz/*'])
|
2026-10-04 02:58:06 +00:00
|
|
|
|
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('<iframe', popup)
|
|
|
|
|
|
self.assertNotIn('human-username', popup)
|
|
|
|
|
|
self.assertNotIn('human-password', popup)
|
|
|
|
|
|
self.assertNotIn('juice-panel', popup)
|
|
|
|
|
|
|
|
|
|
|
|
def test_footer_css_and_hero_copy(self):
|
2026-10-04 08:05:18 +00:00
|
|
|
|
for name in ('index.html',):
|
2026-10-04 02:58:06 +00:00
|
|
|
|
html = (ROOT / name).read_text()
|
|
|
|
|
|
self.assertIn('🍊 → 💧 → 🤖', html)
|
|
|
|
|
|
self.assertIn('>put plainly</span>', html)
|
|
|
|
|
|
self.assertNotIn('Persimmons are', html)
|
|
|
|
|
|
self.assertNotIn('without paperwork', html)
|
|
|
|
|
|
self.assertNotIn('<div class="footerNote">', html)
|
2026-10-04 08:05:18 +00:00
|
|
|
|
hosted = (ROOT / 'index.html').read_text()
|
2026-10-04 02:58:06 +00:00
|
|
|
|
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()
|
2026-10-04 08:05:18 +00:00
|
|
|
|
page.feed((ROOT / 'backend/web/signin.html').read_text())
|
2026-10-04 02:58:06 +00:00
|
|
|
|
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'])
|