fixture-based approach to test_notes.py; prove tmp seed notes separation
This commit is contained in:
parent
6be0394931
commit
91e536efb4
@ -2033,6 +2033,7 @@ def sd_cards_eject(is_q1, sim_exec):
|
||||
|
||||
# useful fixtures
|
||||
from test_backup import backup_system
|
||||
from test_bbqr import readback_bbqr, render_bbqr, readback_bbqr_ll
|
||||
from test_bip39pw import set_bip39_pw
|
||||
from test_drv_entro import derive_bip85_secret, activate_bip85_ephemeral
|
||||
from test_ephemeral import generate_ephemeral_words, import_ephemeral_xprv, goto_eph_seed_menu
|
||||
|
||||
@ -256,7 +256,7 @@ def main():
|
||||
# test_nvram_mk4 needs to run without --eff
|
||||
# se2 duress wallet activated as ephemeral seed requires proper `settings.load`
|
||||
test_args = ["--set", "nfc=1"]
|
||||
if test_module == "test_ephemeral.py":
|
||||
if test_module in ["test_ephemeral.py", "test_notes.py"]:
|
||||
test_args = ["--set", "nfc=1", "--set", "vidsk=1"]
|
||||
|
||||
if args.q1 and '--q1' not in test_args:
|
||||
|
||||
@ -2,14 +2,11 @@
|
||||
#
|
||||
# tests for ../shared/notes.py
|
||||
#
|
||||
import pytest, time, os, shutil, json, random, pdb
|
||||
from test_ux import word_menu_entry, enter_complex
|
||||
from binascii import a2b_hex
|
||||
import pytest, time, json, random, os, pdb
|
||||
from helpers import prandom
|
||||
from constants import simulator_fixed_tprv
|
||||
from charcodes import *
|
||||
|
||||
from test_bbqr import readback_bbqr, render_bbqr, readback_bbqr_ll
|
||||
from test_bbqr import readback_bbqr
|
||||
from bbqr import split_qrs
|
||||
|
||||
# All tests in this file are exclusively meant for Q
|
||||
@ -20,11 +17,11 @@ def THIS_FILE_requires_q1(is_q1):
|
||||
raise pytest.skip('Q1 only')
|
||||
|
||||
@pytest.fixture
|
||||
def goto_notes(cap_story, cap_menu, need_keypress, goto_home, pick_menu_item):
|
||||
def goto_notes(cap_story, cap_menu, press_select, goto_home, pick_menu_item):
|
||||
# drill to the notes menu
|
||||
def doit(item=None):
|
||||
mt = 'Secure Notes & Passwords'
|
||||
goto_home()
|
||||
goto_home() # TODO this is probably why all menus are properly generated
|
||||
m = cap_menu()
|
||||
if mt in m:
|
||||
pick_menu_item(mt)
|
||||
@ -35,7 +32,7 @@ def goto_notes(cap_story, cap_menu, need_keypress, goto_home, pick_menu_item):
|
||||
title, story = cap_story()
|
||||
if title == 'Secure Notes':
|
||||
# enable feature
|
||||
need_keypress('y')
|
||||
press_select()
|
||||
|
||||
if item:
|
||||
pick_menu_item(item)
|
||||
@ -52,96 +49,305 @@ def need_some_notes(settings_get, settings_set):
|
||||
return notes
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def need_some_passwords(settings_get, settings_set):
|
||||
def doit():
|
||||
notes = settings_get('notes', [])
|
||||
if any(n.get('password', False) for n in notes):
|
||||
settings_set('notes', [
|
||||
{'misc': 'More Notes AAAA',
|
||||
'password': 'fds65fd5f1sd51s',
|
||||
'site': 'https://a.com',
|
||||
'title': 'A',
|
||||
'user': 'AAA'},
|
||||
{'misc': 'More Notes BBB',
|
||||
'password': 'default',
|
||||
'site': 'www.site.b.com',
|
||||
'title': 'B-Title',
|
||||
'user': 'Buzzer'}
|
||||
])
|
||||
return notes
|
||||
return doit
|
||||
|
||||
@pytest.mark.parametrize('n_title', [ 'a', 'aaa', 'b'*32])
|
||||
@pytest.mark.parametrize('n_body', [ 'short', 'very long '*30])
|
||||
def test_build_note(n_title, n_body, goto_notes, pick_menu_item, enter_text, cap_menu, cap_story, need_keypress, cap_screen_qr, readback_bbqr, nfc_read_text):
|
||||
@pytest.fixture
|
||||
def delete_note(press_select, goto_notes, cap_menu, pick_menu_item,
|
||||
cap_story):
|
||||
def doit(n_title):
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
found = [i for i in m if f': {n_title}' in i]
|
||||
assert found
|
||||
pick_menu_item(found[-1])
|
||||
|
||||
# we don't try to preserve leading/trailing spaces on note bodies
|
||||
n_body= n_body.strip()
|
||||
|
||||
goto_notes('New Note')
|
||||
|
||||
# create
|
||||
enter_text(n_title)
|
||||
enter_text(n_body, multiline=True)
|
||||
|
||||
# view
|
||||
time.sleep(0.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == f'"{n_title}"'
|
||||
assert m[1] == 'View Note'
|
||||
assert m[-1] == 'Export'
|
||||
|
||||
# test readback
|
||||
for mi in ['View Note', f'"{n_title}"']:
|
||||
time.sleep(0.1)
|
||||
pick_menu_item(mi)
|
||||
pick_menu_item('Delete')
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert story == n_body
|
||||
assert 'SURE' in title
|
||||
assert 'Everything about this' in story
|
||||
|
||||
# back to top notes menu
|
||||
press_select()
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def build_note(goto_notes, pick_menu_item, enter_text, cap_menu, cap_story,
|
||||
need_keypress, cap_screen_qr, readback_bbqr, nfc_read_text,
|
||||
press_select, press_cancel):
|
||||
def doit(n_title, n_body):
|
||||
# we don't try to preserve leading/trailing spaces on note bodies
|
||||
n_body= n_body.strip()
|
||||
|
||||
goto_notes('New Note')
|
||||
|
||||
# create
|
||||
enter_text(n_title)
|
||||
enter_text(n_body, multiline=True)
|
||||
|
||||
# view
|
||||
time.sleep(0.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == f'"{n_title}"'
|
||||
assert m[1] == 'View Note'
|
||||
assert m[2] == 'Edit'
|
||||
assert m[3] == 'Delete'
|
||||
assert m[4] == 'Export'
|
||||
|
||||
# test readback
|
||||
for mi in ['View Note', f'"{n_title}"']:
|
||||
time.sleep(0.1)
|
||||
pick_menu_item(mi)
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert story == n_body
|
||||
need_keypress(KEY_QR)
|
||||
qr_rb = cap_screen_qr().decode('utf-8')
|
||||
assert qr_rb == n_body
|
||||
press_cancel()
|
||||
|
||||
# hidden QR button on menu feature
|
||||
m = cap_menu()
|
||||
assert m[1] == 'View Note'
|
||||
need_keypress(KEY_QR)
|
||||
qr_rb = cap_screen_qr().decode('utf-8')
|
||||
assert qr_rb == n_body
|
||||
press_cancel()
|
||||
|
||||
# hidden NFC button on menu feature
|
||||
m = cap_menu()
|
||||
assert m[1] == 'View Note'
|
||||
need_keypress(KEY_NFC)
|
||||
time.sleep(.1)
|
||||
nfc_rb = nfc_read_text()
|
||||
time.sleep(.1)
|
||||
assert nfc_rb == n_body
|
||||
press_cancel()
|
||||
|
||||
# export
|
||||
pick_menu_item('Export')
|
||||
title, story = cap_story()
|
||||
assert 'Export' in title
|
||||
assert 'to save note to SD' in story
|
||||
assert 'to show QR' in story
|
||||
assert 'WARNING' in story
|
||||
assert 'will be cleartext' in story
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
file_type, data = readback_bbqr()
|
||||
assert file_type == 'J'
|
||||
obj = json.loads(data)
|
||||
assert obj.keys() == {'coldcard_notes'}
|
||||
obj = obj['coldcard_notes']
|
||||
assert len(obj) == 1
|
||||
obj = obj[0]
|
||||
assert obj['title'] == n_title
|
||||
assert obj['misc'] == n_body
|
||||
|
||||
# back to top notes menu
|
||||
press_select()
|
||||
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def build_password(goto_notes, pick_menu_item, enter_text, cap_menu, cap_story,
|
||||
need_keypress, cap_screen_qr, readback_bbqr, nfc_read_text, cap_text_box,
|
||||
settings_get, settings_set, scan_a_qr, press_select, press_cancel):
|
||||
|
||||
def doit(n_title, n_user=None, n_pw=None, n_site=None, n_body=None, key_pw=None):
|
||||
goto_notes('New Password')
|
||||
enter_text(n_title)
|
||||
if n_user:
|
||||
enter_text(n_user)
|
||||
else:
|
||||
press_select()
|
||||
|
||||
if key_pw and key_pw == KEY_QR:
|
||||
need_keypress(KEY_QR)
|
||||
time.sleep(1.1)
|
||||
scan_a_qr(n_pw)
|
||||
time.sleep(1.1)
|
||||
|
||||
elif key_pw:
|
||||
# function keys: let it auto gen
|
||||
need_keypress(key_pw)
|
||||
time.sleep(0.1)
|
||||
if key_pw == KEY_F5: # bip-85
|
||||
enter_text('34')
|
||||
time.sleep(0.1)
|
||||
n_pw = ''.join(cap_text_box()).strip()
|
||||
assert n_pw and len(n_pw) > 10
|
||||
need_keypress(KEY_ENTER)
|
||||
else:
|
||||
enter_text(n_pw)
|
||||
|
||||
if n_site:
|
||||
enter_text(n_site)
|
||||
else:
|
||||
press_select()
|
||||
|
||||
if n_body:
|
||||
enter_text(n_body, multiline=True)
|
||||
else:
|
||||
press_cancel()
|
||||
|
||||
# view
|
||||
time.sleep(0.1)
|
||||
m = cap_menu()
|
||||
N = 1
|
||||
assert m[0] == f'"{n_title}"'
|
||||
if n_user and not n_site:
|
||||
assert n_user in m[1]
|
||||
N += 1
|
||||
elif n_site and not n_user:
|
||||
assert n_site in m[1]
|
||||
N += 1
|
||||
elif n_site and n_user:
|
||||
assert n_user in m[1]
|
||||
assert n_site in m[2]
|
||||
N += 2
|
||||
|
||||
assert 'View Password' in m
|
||||
assert 'Send Password' in m
|
||||
assert 'Export' in m
|
||||
assert 'Edit Metadata' in m
|
||||
assert 'Delete' in m
|
||||
assert 'Change Password' in m
|
||||
|
||||
# top 3 menu items do same thing: view details
|
||||
for idx in range(N):
|
||||
pick_menu_item(m[idx])
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
if n_user:
|
||||
assert f'User: {n_user}' in story
|
||||
if n_site:
|
||||
assert f'Site: {n_site}' in story
|
||||
assert 'Password: (' in story
|
||||
if n_body:
|
||||
assert 'Notes:' in story
|
||||
assert story.endswith(n_body)
|
||||
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# view pw as text and QR
|
||||
pick_menu_item('View Password')
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert story == n_pw
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
qr_rb = cap_screen_qr().decode('utf-8')
|
||||
assert qr_rb == n_pw
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# hidden QR button on menu feature
|
||||
m = cap_menu()
|
||||
assert m[1] == 'View Note'
|
||||
need_keypress(KEY_QR)
|
||||
qr_rb = cap_screen_qr().decode('utf-8')
|
||||
assert qr_rb == n_body
|
||||
need_keypress(KEY_CANCEL)
|
||||
return doit
|
||||
|
||||
# hidden NFC button on menu feature
|
||||
m = cap_menu()
|
||||
assert m[1] == 'View Note'
|
||||
need_keypress(KEY_NFC)
|
||||
nfc_rb = nfc_read_text()
|
||||
assert nfc_rb == n_body
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# export
|
||||
m = cap_menu()
|
||||
pick_menu_item('Export')
|
||||
title, story = cap_story()
|
||||
assert 'Export' in title
|
||||
assert 'to save note to SD' in story
|
||||
assert 'to show QR' in story
|
||||
assert 'WARNING' in story
|
||||
assert 'will be cleartext' in story
|
||||
@pytest.fixture
|
||||
def change_password(goto_notes, pick_menu_item, enter_text, cap_story, need_keypress,
|
||||
settings_get, press_select, press_cancel, cap_menu):
|
||||
def doit(id_title, new_title=None, new_username=None, new_site=None,
|
||||
new_misc=None, new_password=None):
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
found = [i for i in m if f': {id_title}' in i]
|
||||
assert found
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
file_type, data = readback_bbqr()
|
||||
assert file_type == 'J'
|
||||
obj = json.loads(data)
|
||||
assert obj.keys() == {'coldcard_notes'}
|
||||
obj = obj['coldcard_notes']
|
||||
assert len(obj) == 1
|
||||
obj = obj[0]
|
||||
assert obj['title'] == n_title
|
||||
assert obj['misc'] == n_body
|
||||
pick_menu_item(found[0])
|
||||
|
||||
# drill back to it
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
found = [i for i in m if f': {n_title}' in i]
|
||||
assert found
|
||||
pick_menu_item(found[-1])
|
||||
if new_title or new_username or new_site or new_misc:
|
||||
need_in_story = []
|
||||
pick_menu_item('Edit Metadata')
|
||||
if new_title:
|
||||
enter_text(KEY_CLEAR + new_title)
|
||||
need_in_story.append('Title')
|
||||
else:
|
||||
press_select()
|
||||
if new_username:
|
||||
enter_text(KEY_CLEAR + new_username)
|
||||
need_in_story.append('Username')
|
||||
else:
|
||||
press_select()
|
||||
if new_site:
|
||||
enter_text(KEY_CLEAR + new_site)
|
||||
need_in_story.append('Site Name')
|
||||
else:
|
||||
press_select()
|
||||
if new_misc:
|
||||
enter_text(KEY_CLEAR + new_misc, multiline=True)
|
||||
need_in_story.append('Other Notes')
|
||||
else:
|
||||
press_cancel()
|
||||
|
||||
pick_menu_item('Delete')
|
||||
title, story = cap_story()
|
||||
assert 'SURE' in title
|
||||
assert 'Everything about this' in story
|
||||
# approve change
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert 'SURE' in title
|
||||
for i in need_in_story:
|
||||
assert i in story
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
if new_password:
|
||||
pick_menu_item('Change Password')
|
||||
enter_text(KEY_CLEAR + new_password)
|
||||
|
||||
# confirm
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert 'Confirm' in title
|
||||
assert 'New Password' in story
|
||||
assert 'Old Password' in story
|
||||
assert new_password in story
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
# test changes at low-level
|
||||
time.sleep(0.1)
|
||||
notes = settings_get('notes')
|
||||
note = [n for n in notes if n['title'] == (new_title or id_title)][0]
|
||||
assert note
|
||||
if new_site:
|
||||
assert note['site'] == new_site
|
||||
if new_username:
|
||||
assert note['user'] == new_username
|
||||
if new_misc:
|
||||
assert note['misc'] == new_misc
|
||||
if new_password:
|
||||
assert note['password'] == new_password
|
||||
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.mark.parametrize('n_title', [ 'a', 'aaa', 'b'*32])
|
||||
@pytest.mark.parametrize('n_body', [ 'short', 'very long '*30])
|
||||
def test_build_note(n_title, n_body, build_note, delete_note):
|
||||
build_note(n_title, n_body)
|
||||
delete_note(n_title)
|
||||
|
||||
# back to top notes menu
|
||||
need_keypress(KEY_ENTER)
|
||||
m = cap_menu()
|
||||
assert ('Export All' in m) or ('Disable Feature' in m)
|
||||
|
||||
@pytest.mark.parametrize('size', [ 4000, 30000])
|
||||
@pytest.mark.parametrize('encoding', '2Z' )
|
||||
def test_huge_notes(size, encoding, goto_notes, pick_menu_item, enter_text, cap_menu, cap_story, need_keypress, cap_screen_qr, readback_bbqr, scan_a_qr, settings_set, settings_get):
|
||||
def test_huge_notes(size, encoding, goto_notes, enter_text, cap_menu, need_keypress,
|
||||
scan_a_qr, settings_set, settings_get):
|
||||
|
||||
# Since we don't limit note sizes, by request of NVK ... test them
|
||||
|
||||
@ -176,129 +382,41 @@ def test_huge_notes(size, encoding, goto_notes, pick_menu_item, enter_text, cap_
|
||||
settings_set('notes', [])
|
||||
goto_notes() # redraw
|
||||
|
||||
@pytest.mark.parametrize('key', 'AB' + KEY_F1 + KEY_F2 + KEY_F3 + KEY_F4 + KEY_F5 + KEY_QR)
|
||||
def test_build_password(key, goto_notes, pick_menu_item, enter_text, cap_menu, cap_story, need_keypress, cap_screen_qr, readback_bbqr, nfc_read_text, cap_text_box, settings_get, settings_set, scan_a_qr):
|
||||
|
||||
@pytest.mark.parametrize('key', [None, KEY_QR])
|
||||
@pytest.mark.parametrize('site', ["https://feed.org", None])
|
||||
@pytest.mark.parametrize('user', [None, "joe"])
|
||||
@pytest.mark.parametrize('misc', [None, "bla bla bla bla"])
|
||||
def test_password_flow(key, site, user, misc, build_password, change_password):
|
||||
# Test password entry, including all the auto-generation capabilities
|
||||
case = '0x%02x' % ord(key)
|
||||
|
||||
n_title = f'Title {case}'
|
||||
n_user = f'Username {case}'
|
||||
n_pw = None
|
||||
n_site = f'Site {case}'
|
||||
n_body = f'More Notes {case}'
|
||||
|
||||
# create
|
||||
goto_notes('New Password')
|
||||
enter_text(n_title)
|
||||
enter_text(n_user)
|
||||
if key == 'A':
|
||||
n_pw = 'A' * 99
|
||||
enter_text(n_pw)
|
||||
elif key == 'B':
|
||||
n_pw = 'B' * 3
|
||||
enter_text(n_pw)
|
||||
elif key == KEY_QR:
|
||||
n_pw = 'QR rocks'
|
||||
need_keypress(KEY_QR)
|
||||
time.sleep(1.1)
|
||||
scan_a_qr(n_pw)
|
||||
time.sleep(1.1)
|
||||
need_keypress(KEY_ENTER)
|
||||
else:
|
||||
# function keys: let it auto gen
|
||||
need_keypress(key)
|
||||
time.sleep(0.1)
|
||||
if key == KEY_F5: # bip-85
|
||||
enter_text('34')
|
||||
time.sleep(0.1)
|
||||
n_pw = ''.join(cap_text_box()).strip()
|
||||
assert n_pw and len(n_pw) > 10
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
enter_text(n_site)
|
||||
enter_text(n_body, multiline=True)
|
||||
|
||||
# view
|
||||
time.sleep(0.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == f'"{n_title}"'
|
||||
assert n_user in m[1]
|
||||
assert n_site in m[2]
|
||||
assert 'Export' in m
|
||||
|
||||
# top 3 menu items do same thing: view details
|
||||
for idx in range(3):
|
||||
pick_menu_item(m[idx])
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert f'User: {n_user}' in story
|
||||
assert f'Site: {n_site}' in story
|
||||
assert 'Password: (' in story
|
||||
assert 'Notes:' in story
|
||||
assert story.endswith(n_body)
|
||||
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# view pw as text and QR
|
||||
pick_menu_item('View Password')
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert story == n_pw
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
qr_rb = cap_screen_qr().decode('utf-8')
|
||||
assert qr_rb == n_pw
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# change stuff
|
||||
pick_menu_item('Edit Metadata')
|
||||
mod = ' CHG%04d' % random.randint(1000, 9999)
|
||||
enter_text(mod)
|
||||
enter_text(mod)
|
||||
enter_text(mod)
|
||||
enter_text(KEY_CLEAR + n_body + mod, multiline=True)
|
||||
|
||||
# approve change
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert 'SURE' in title
|
||||
assert 'Site Name' in story
|
||||
assert 'Title' in story
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
pick_menu_item('Change Password')
|
||||
enter_text(KEY_CLEAR + 'default')
|
||||
|
||||
# confirm
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert 'Confirm' in title
|
||||
assert 'New Password' in story
|
||||
assert 'default' in story
|
||||
assert 'Old Password' in story
|
||||
assert n_pw in story
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
# test changes at low-level
|
||||
time.sleep(0.1)
|
||||
notes = settings_get('notes')
|
||||
note = [n for n in notes if n['title'] == n_title+mod][0]
|
||||
assert note['site'] == n_site + mod
|
||||
assert note['user'] == n_user + mod
|
||||
assert note['misc'] == n_body + mod
|
||||
assert note['password'] == 'default'
|
||||
|
||||
# wipe & redraw
|
||||
settings_set('notes', notes[0:-3])
|
||||
goto_notes()
|
||||
title = os.urandom(4).hex()
|
||||
build_password(n_title=title, n_user=user, n_pw='A'*99,
|
||||
n_site=site, n_body=misc,
|
||||
key_pw=None)
|
||||
change_password(id_title=title,
|
||||
new_username="changed" if user is None else None,
|
||||
new_site="https://changed.org" if site is None else None,
|
||||
new_misc="new bla newer bla newest bla" if misc is None else None)
|
||||
|
||||
|
||||
def test_top_export(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress, settings_get, settings_set, readback_bbqr, need_some_notes):
|
||||
@pytest.mark.parametrize('key', [KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5])
|
||||
def test_password_flow_gen(key, build_password, change_password):
|
||||
title = os.urandom(4).hex()
|
||||
build_password(n_title=title, n_pw='B'*3, key_pw=key)
|
||||
change_password(id_title=title, new_password="changed")
|
||||
|
||||
notes = need_some_notes()
|
||||
|
||||
def test_password_change_title(build_password, change_password):
|
||||
build_password(n_title="old_title", n_pw="default", key_pw=None)
|
||||
change_password(id_title="old_title", new_title="new_title")
|
||||
|
||||
|
||||
def test_top_export(goto_notes, pick_menu_item, cap_story, need_keypress, settings_get,
|
||||
readback_bbqr, need_some_notes):
|
||||
|
||||
notes = settings_get('notes', [])
|
||||
assert len(notes) >= 1
|
||||
if not len(notes):
|
||||
notes = need_some_notes()
|
||||
|
||||
goto_notes()
|
||||
pick_menu_item('Export All')
|
||||
@ -318,8 +436,8 @@ def test_top_export(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypre
|
||||
assert obj['coldcard_notes'] == notes
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
def test_top_import(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress, settings_get, settings_set, scan_a_qr, need_some_notes):
|
||||
|
||||
def test_top_import(goto_notes, cap_menu, cap_story, need_keypress, settings_get,
|
||||
settings_set, scan_a_qr, need_some_notes):
|
||||
# make some
|
||||
notes = need_some_notes()
|
||||
|
||||
@ -342,195 +460,26 @@ def test_top_import(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypre
|
||||
|
||||
for p in parts:
|
||||
scan_a_qr(p)
|
||||
|
||||
|
||||
time.sleep(.5) # decompression time in some cases
|
||||
m = cap_menu()
|
||||
assert notes[0]['title'] in m[0]
|
||||
assert settings_get('notes', 'MISSING') == notes
|
||||
goto_notes()
|
||||
|
||||
@pytest.mark.parametrize('key', 'AB' + KEY_F1 + KEY_F2 + KEY_F3 + KEY_F4 + KEY_F5 + KEY_QR)
|
||||
def test_build_password(key, goto_notes, pick_menu_item, enter_text, cap_menu, cap_story, need_keypress, cap_screen_qr, readback_bbqr, nfc_read_text, cap_text_box, settings_get, settings_set, scan_a_qr):
|
||||
# Test password entry, including all the auto-generation capabilities
|
||||
case = '0x%02x' % ord(key)
|
||||
|
||||
n_title = f'Title {case}'
|
||||
n_user = f'Username {case}'
|
||||
n_pw = None
|
||||
n_site = f'Site {case}'
|
||||
n_body = f'More Notes {case}'
|
||||
|
||||
# create
|
||||
goto_notes('New Password')
|
||||
enter_text(n_title)
|
||||
enter_text(n_user)
|
||||
if key == 'A':
|
||||
n_pw = 'A' * 99
|
||||
enter_text(n_pw)
|
||||
elif key == 'B':
|
||||
n_pw = 'B' * 3
|
||||
enter_text(n_pw)
|
||||
elif key == KEY_QR:
|
||||
n_pw = 'QR rocks'
|
||||
need_keypress(KEY_QR)
|
||||
time.sleep(1.1)
|
||||
scan_a_qr(n_pw)
|
||||
time.sleep(1.1)
|
||||
need_keypress(KEY_ENTER)
|
||||
else:
|
||||
# function keys: let it auto gen
|
||||
need_keypress(key)
|
||||
time.sleep(0.1)
|
||||
if key == KEY_F5: # bip-85
|
||||
enter_text('34')
|
||||
time.sleep(0.1)
|
||||
n_pw = ''.join(cap_text_box()).strip()
|
||||
assert n_pw and len(n_pw) > 10
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
enter_text(n_site)
|
||||
enter_text(n_body, multiline=True)
|
||||
|
||||
# view
|
||||
time.sleep(0.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == f'"{n_title}"'
|
||||
assert n_user in m[1]
|
||||
assert n_site in m[2]
|
||||
assert 'Export' in m
|
||||
|
||||
# top 3 menu items do same thing: view details
|
||||
for idx in range(3):
|
||||
pick_menu_item(m[idx])
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert f'User: {n_user}' in story
|
||||
assert f'Site: {n_site}' in story
|
||||
assert 'Password: (' in story
|
||||
assert 'Notes:' in story
|
||||
assert story.endswith(n_body)
|
||||
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# view pw as text and QR
|
||||
pick_menu_item('View Password')
|
||||
title, story = cap_story()
|
||||
assert title == n_title
|
||||
assert story == n_pw
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
qr_rb = cap_screen_qr().decode('utf-8')
|
||||
assert qr_rb == n_pw
|
||||
need_keypress(KEY_CANCEL)
|
||||
|
||||
# change stuff
|
||||
pick_menu_item('Edit Metadata')
|
||||
mod = ' CHG%04d' % random.randint(1000, 9999)
|
||||
enter_text(mod)
|
||||
enter_text(mod)
|
||||
enter_text(mod)
|
||||
enter_text(KEY_CLEAR + n_body + mod, multiline=True)
|
||||
|
||||
# approve change
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert 'SURE' in title
|
||||
assert 'Site Name' in story
|
||||
assert 'Title' in story
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
pick_menu_item('Change Password')
|
||||
enter_text(KEY_CLEAR + 'default')
|
||||
|
||||
# confirm
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert 'Confirm' in title
|
||||
assert 'New Password' in story
|
||||
assert 'default' in story
|
||||
assert 'Old Password' in story
|
||||
assert n_pw in story
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
# test changes at low-level
|
||||
time.sleep(0.1)
|
||||
notes = settings_get('notes')
|
||||
note = [n for n in notes if n['title'] == n_title+mod][0]
|
||||
assert note['site'] == n_site + mod
|
||||
assert note['user'] == n_user + mod
|
||||
assert note['misc'] == n_body + mod
|
||||
assert note['password'] == 'default'
|
||||
|
||||
# wipe & redraw
|
||||
settings_set('notes', notes[0:-3])
|
||||
goto_notes()
|
||||
|
||||
|
||||
def test_top_export(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress, settings_get, settings_set, readback_bbqr, need_some_notes):
|
||||
|
||||
notes = need_some_notes()
|
||||
|
||||
notes = settings_get('notes', [])
|
||||
assert len(notes) >= 1
|
||||
|
||||
goto_notes()
|
||||
pick_menu_item('Export All')
|
||||
|
||||
title, story = cap_story()
|
||||
assert 'Export' in title
|
||||
assert 'to SD Card' in story
|
||||
assert 'to show QR' in story
|
||||
assert 'WARNING' in story
|
||||
assert 'will be cleartext' in story
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
file_type, data = readback_bbqr()
|
||||
assert file_type == 'J'
|
||||
obj = json.loads(data)
|
||||
assert obj.keys() == {'coldcard_notes'}
|
||||
assert obj['coldcard_notes'] == notes
|
||||
need_keypress(KEY_ENTER)
|
||||
|
||||
def test_top_import(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress, settings_get, settings_set, scan_a_qr, need_some_notes):
|
||||
|
||||
# make some
|
||||
notes = need_some_notes()
|
||||
|
||||
# wipe them
|
||||
settings_set('notes', [])
|
||||
|
||||
goto_notes('Import')
|
||||
title, story = cap_story()
|
||||
assert 'Import' in title
|
||||
assert 'from SD Card' in story
|
||||
assert 'to scan QR' in story
|
||||
assert 'WARNING' not in story
|
||||
|
||||
jj = json.dumps(dict(coldcard_notes=notes))
|
||||
|
||||
need_keypress(KEY_QR)
|
||||
|
||||
_, parts = split_qrs(jj, 'J', max_version=20)
|
||||
random.shuffle(parts)
|
||||
|
||||
for p in parts:
|
||||
scan_a_qr(p)
|
||||
|
||||
time.sleep(.5) # decompression time in some cases
|
||||
m = cap_menu()
|
||||
assert notes[0]['title'] in m[0]
|
||||
mm = [n.split(":")[-1].strip() for n in m if ":" in n]
|
||||
for note in notes:
|
||||
assert note['title'] in mm
|
||||
assert settings_get('notes', 'MISSING') == notes
|
||||
goto_notes()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('qr,title', [
|
||||
('otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30', 'ACME Co:john.doe@email.com'),
|
||||
('otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30',
|
||||
'ACME Co:john.doe@email.com'),
|
||||
('otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi',
|
||||
'pi@raspberrypi'),
|
||||
'pi@raspberrypi'),
|
||||
('otpauth-migration://offline?data=CiAKCghCEIa1rWta1rUSDEV4YW1wbGUgRGF0YSABKAEwAhAB',
|
||||
'Google Auth'),
|
||||
'Google Auth'),
|
||||
])
|
||||
def test_top_qr(qr, title, goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress, settings_get, settings_set, scan_a_qr):
|
||||
def test_top_qr(qr, title, goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress,
|
||||
settings_get, settings_set, scan_a_qr):
|
||||
# import some fun QR codes (will be notes) from top-level, undocumented
|
||||
|
||||
goto_notes()
|
||||
@ -549,7 +498,7 @@ def test_top_qr(qr, title, goto_notes, pick_menu_item, cap_menu, cap_story, need
|
||||
#need_keypress(KEY_ENTER)
|
||||
|
||||
|
||||
def test_top_disable(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypress, settings_get, settings_set):
|
||||
def test_top_disable(goto_notes, pick_menu_item, cap_menu, settings_get, settings_set):
|
||||
# Keep last - disables, deletes notes
|
||||
settings_set('notes', [])
|
||||
goto_notes()
|
||||
@ -561,4 +510,100 @@ def test_top_disable(goto_notes, pick_menu_item, cap_menu, cap_story, need_keypr
|
||||
assert 'Ready To Sign' in m
|
||||
assert settings_get('notes', 'MISSING') == 'MISSING'
|
||||
|
||||
|
||||
def test_tmp_notes_separation(goto_notes, pick_menu_item, generate_ephemeral_words,
|
||||
build_note, build_password, seed_vault_enable, cap_menu,
|
||||
restore_main_seed, press_select, goto_home):
|
||||
seed_vault_enable()
|
||||
goto_notes()
|
||||
# create some notes in master settings
|
||||
build_note(n_title="note-master", n_body="Master seed note meta")
|
||||
build_password(n_title="pwd-master", n_user="ccu", n_pw="fdshjd76342gdhj",
|
||||
n_body="WIF: 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")
|
||||
|
||||
# switch to random ephemeral seed
|
||||
generate_ephemeral_words(12, from_main=True, seed_vault=True)
|
||||
|
||||
time.sleep(.1)
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
assert len(m) == 4 # EMPTY - no saved notes
|
||||
|
||||
build_note(n_title="note-tmp", n_body="Temporary seed note meta")
|
||||
build_password(n_title="pwd-tmp", n_user="ttu", n_pw="n7c4tvb6erdgg8",
|
||||
n_body="HEX: 800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D")
|
||||
|
||||
# switch to yet another random ephemeral seed
|
||||
generate_ephemeral_words(24, from_main=False, seed_vault=True)
|
||||
|
||||
time.sleep(.1)
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
assert len(m) == 4 # EMPTY - no saved notes
|
||||
|
||||
build_note(n_title="note-tmp2", n_body="Second Temporary seed note meta")
|
||||
|
||||
# back to master
|
||||
restore_main_seed(seed_vault=True)
|
||||
time.sleep(.1)
|
||||
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
mm = [n.split(":")[-1].strip() for n in m if ":" in n]
|
||||
assert 'note-master' in mm
|
||||
assert 'pwd-master' in mm
|
||||
assert 'note-tmp' not in mm
|
||||
assert 'pwd-tmp' not in mm
|
||||
assert 'note-tmp2' not in mm
|
||||
|
||||
goto_home()
|
||||
pick_menu_item("Seed Vault")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
|
||||
# first tmp
|
||||
pick_menu_item(m[0])
|
||||
pick_menu_item("Use This Seed")
|
||||
press_select()
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
mm = [n.split(":")[-1].strip() for n in m if ":" in n]
|
||||
assert 'note-master' not in mm
|
||||
assert 'pwd-master' not in mm
|
||||
assert 'note-tmp' in mm
|
||||
assert 'pwd-tmp' in mm
|
||||
assert 'note-tmp2' not in mm
|
||||
|
||||
goto_home()
|
||||
pick_menu_item("Seed Vault")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
|
||||
# second tmp (directly from first tmp)
|
||||
pick_menu_item(m[1])
|
||||
pick_menu_item("Use This Seed")
|
||||
press_select()
|
||||
goto_notes()
|
||||
|
||||
m = cap_menu()
|
||||
mm = [n.split(":")[-1].strip() for n in m if ":" in n]
|
||||
assert 'note-master' not in mm
|
||||
assert 'pwd-master' not in mm
|
||||
assert 'note-tmp' not in mm
|
||||
assert 'pwd-tmp' not in mm
|
||||
assert 'note-tmp2' in mm
|
||||
|
||||
# back to master (again)
|
||||
restore_main_seed(seed_vault=True)
|
||||
time.sleep(.1)
|
||||
|
||||
goto_notes()
|
||||
m = cap_menu()
|
||||
mm = [n.split(":")[-1].strip() for n in m if ":" in n]
|
||||
assert 'note-master' in mm
|
||||
assert 'pwd-master' in mm
|
||||
assert 'note-tmp' not in mm
|
||||
assert 'pwd-tmp' not in mm
|
||||
assert 'note-tmp2' not in mm
|
||||
|
||||
# EOF
|
||||
|
||||
Loading…
Reference in New Issue
Block a user