Skip to content
This repository has been archived by the owner on Sep 1, 2021. It is now read-only.

Commit

Permalink
Account creation screen and tests, fixes #13
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreMiras committed Apr 13, 2018
1 parent a694271 commit 1ac5a75
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 20 deletions.
34 changes: 32 additions & 2 deletions src/etheroll.kv
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,35 @@
PushUp:


<CreateNewAccount>:
orientation: 'vertical'
MDTextField:
id: new_password1_id
hint_text: "Password"
helper_text: "Enter the password for encrypting your new account"
helper_text_mode: "on_focus"
password: True
write_tab: False
text: root.new_password1
on_text: root.new_password1 = args[1]
MDTextField:
id: new_password2_id
# TODO: do validation as we type
hint_text: "Password (again)"
helper_text: "Retype your password"
helper_text_mode: "on_focus"
password: True
write_tab: False
text: root.new_password2
on_text: root.new_password2 = args[1]
AnchorLayout:
MDRaisedButton:
id: create_account_button_id
text: "Create account"
on_release: root.create_account()
PushUp:


<SwitchAccountScreen>:
name: 'switch_account'
on_pre_enter:
Expand All @@ -59,11 +88,12 @@
SwitchAccount:
id: switch_account_id
MDBottomNavigationItem:
id: create_new_account_nav_item_id
name: "create"
text: "Create"
icon: "plus"
MDLabel:
text: "Not implemented"
CreateNewAccount:
id: create_new_account_id
MDBottomNavigationItem:
name: "import"
text: "Import"
Expand Down
126 changes: 118 additions & 8 deletions src/etheroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from raven.conf import setup_logging
from raven.handlers.logging import SentryHandler

from ethereum_utils import AccountUtils
from utils import Dialog, patch_find_library_android, patch_typing_python351
from version import __version__

Expand Down Expand Up @@ -70,7 +71,7 @@ def load_account_list(self):
self.controller = App.get_running_app().root
account_list_id = self.ids.account_list_id
account_list_id.clear_widgets()
accounts = self.controller.pyethapp.services.accounts
accounts = self.controller.account_utils.get_account_list()
if len(accounts) == 0:
self.on_empty_account_list()
for account in accounts:
Expand All @@ -80,13 +81,123 @@ def load_account_list(self):
@staticmethod
def on_empty_account_list():
controller = App.get_running_app().root
keystore_dir = controller.pyethapp.services.accounts.keystore_dir
keystore_dir = controller.account_utils.keystore_dir
title = "No account found"
body = "No account found in:\n%s" % keystore_dir
dialog = Dialog.create_dialog(title, body)
dialog.open()


class CreateNewAccount(BoxLayout):
"""
Makes it possible to create json keyfiles.
"""

new_password1 = StringProperty()
new_password2 = StringProperty()

def __init__(self, **kwargs):
super(CreateNewAccount, self).__init__(**kwargs)
Clock.schedule_once(lambda dt: self.setup())

def setup(self):
"""
Sets security vs speed default values.
Plus hides the advanced widgets.
"""
self.controller = App.get_running_app().root

def verify_password_field(self):
"""
Makes sure passwords are matching and are not void.
"""
passwords_matching = self.new_password1 == self.new_password2
passwords_not_void = self.new_password1 != ''
return passwords_matching and passwords_not_void

def verify_fields(self):
"""
Verifies password fields are valid.
"""
return self.verify_password_field()

@staticmethod
def try_unlock(account, password):
"""
Just as a security measure, verifies we can unlock
the newly created account with provided password.
"""
# making sure it's locked first
account.lock()
try:
account.unlock(password)
except ValueError:
title = "Unlock error"
body = ""
body += "Couldn't unlock your account.\n"
body += "The issue should be reported."
dialog = Dialog.create_dialog(title, body)
dialog.open()
return

# @mainthread
def on_account_created(self, account):
"""
Switches to the newly created account.
Clears the form.
"""
self.controller.switch_account_screen.current_account = account
self.new_password1 = ''
self.new_password2 = ''

# @mainthread
def toggle_widgets(self, enabled):
"""
Enables/disables account creation widgets.
"""
self.disabled = not enabled

def show_redirect_dialog(self):
title = "Account created, redirecting..."
body = ""
body += "Your account was created, "
body += "you will be redirected to the overview."
dialog = Dialog.create_dialog(title, body)
dialog.open()

def load_landing_page(self):
"""
Returns to the landing page.
"""
screen_manager = self.controller.screen_manager
screen_manager.transition.direction = 'right'
screen_manager.current = 'roll_screen'

# @run_in_thread
def create_account(self):
"""
Creates an account from provided form.
Verify we can unlock it.
Disables widgets during the process, so the user doesn't try
to create another account during the process.
"""
self.toggle_widgets(False)
if not self.verify_fields():
Dialog.show_invalid_form_dialog()
self.toggle_widgets(True)
return
password = self.new_password1
Dialog.snackbar_message("Creating account...")
account = self.controller.account_utils.new_account(password=password)
Dialog.snackbar_message("Created!")
self.toggle_widgets(True)
self.on_account_created(account)
CreateNewAccount.try_unlock(account, password)
self.show_redirect_dialog()
self.load_landing_page()
return account


class CustomToolbar(Toolbar):
"""
Toolbar with helper method for loading default/back buttons.
Expand Down Expand Up @@ -334,12 +445,7 @@ def _after_init(self, dt):
def _init_pyethapp(self, keystore_dir=None):
if keystore_dir is None:
keystore_dir = self.get_keystore_path()
# must be imported after `patch_find_library_android()`
from devp2p.app import BaseApp
from pyethapp.accounts import AccountsService
self.pyethapp = BaseApp(
config=dict(accounts=dict(keystore_dir=keystore_dir)))
AccountsService.register_with_app(self.pyethapp)
self.account_utils = AccountUtils(keystore_dir=keystore_dir)

@property
def pyetheroll(self):
Expand Down Expand Up @@ -440,6 +546,10 @@ def update_profit_property(self):
def navigation(self):
return self.ids.navigation_id

@property
def screen_manager(self):
return self.ids.screen_manager_id

@property
def roll_screen(self):
return self.ids.roll_screen_id
Expand Down
Empty file added src/tests/ui/__init__.py
Empty file.
107 changes: 97 additions & 10 deletions src/tests/ui/test_etheroll.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import shutil
import threading
import time
import unittest
from functools import partial
Expand Down Expand Up @@ -38,6 +39,18 @@ def advance_frames(self, count):
for i in range(count):
EventLoop.idle()

def advance_frames_for_screen(self):
"""
Gives screen switch animation time to render.
"""
self.advance_frames(50)

def advance_frames_for_drawer(self):
"""
Gives drawer switch animation time to render.
"""
self.advance_frames_for_screen()

def helper_setup(self, app):
etheroll.SCREEN_SWITCH_DELAY = 0.001

Expand Down Expand Up @@ -81,8 +94,7 @@ def helper_test_toolbar(self, app):
self.assertEqual(len(left_actions.children), 1)
# clicking the menu action item should load the navigation drawer
left_actions.children[0].dispatch('on_release')
# the drawer animation takes time
self.advance_frames(50)
self.advance_frames_for_drawer()
# checking the drawer items
navigation = controller.ids.navigation_id
navigation_drawer = navigation.ids.navigation_drawer_id
Expand All @@ -93,8 +105,7 @@ def helper_test_toolbar(self, app):
# clicking the about one
about_item = navigation_drawer_items[0]
about_item.dispatch('on_release')
# the drawer animation takes time
self.advance_frames(50)
self.advance_frames_for_drawer()
self.assertEqual(
controller.ids.screen_manager_id.current, 'about_screen')
# toolbar should now be loaded with the back button
Expand All @@ -105,8 +116,7 @@ def helper_test_toolbar(self, app):
# going back to main screen
left_actions = toolbar.ids.left_actions
left_actions.children[0].dispatch('on_release')
# loading screen takes time
self.advance_frames(50)
self.advance_frames_for_screen()
self.assertEqual(
controller.ids.screen_manager_id.current, 'roll_screen')

Expand All @@ -121,8 +131,7 @@ def helper_test_about_screen(self, app):
self.assertEqual(screen.name, 'roll_screen')
# loads the about and verify
screen_manager.current = 'about_screen'
# loading screen takes time
self.advance_frames(50)
self.advance_frames_for_screen()
screen = screen_manager.children[0]
self.assertEqual(screen.name, 'about_screen')
# checks about screen content
Expand All @@ -132,8 +141,85 @@ def helper_test_about_screen(self, app):
'https://github.com/AndreMiras/EtherollApp' in about_content)
# loads back the default screen
screen_manager.current = 'roll_screen'
# loading screen takes time
self.advance_frames(50)
self.advance_frames_for_screen()

def helper_test_create_first_account(self, app):
"""
Creates the first account.
"""
controller = app.root
screen_manager = controller.ids.screen_manager_id
account_utils = controller.account_utils
# makes sure no account are loaded
self.assertEqual(len(account_utils.get_account_list()), 0)
# loads the switch account screen
switch_account_screen = controller.switch_account_screen
screen_manager.current = switch_account_screen.name
self.advance_frames_for_screen()
# it should open the warning dialog
dialogs = Dialog.dialogs
self.assertEqual(len(dialogs), 1)
dialog = dialogs[0]
self.assertEqual(dialog.title, 'No account found')
dialog.dismiss()
self.assertEqual(len(dialogs), 0)
# loads the create new account tab
create_new_account_nav_item = \
switch_account_screen.ids.create_new_account_nav_item_id
create_new_account_nav_item.dispatch('on_tab_press')
# verifies no current account is setup
self.assertEqual(switch_account_screen.current_account, None)
# retrieves the create_new_account widget
create_new_account = switch_account_screen.ids.create_new_account_id
# retrieves widgets (password fields, sliders and buttons)
new_password1_id = create_new_account.ids.new_password1_id
new_password2_id = create_new_account.ids.new_password2_id
create_account_button_id = \
create_new_account.ids.create_account_button_id
# fills them up with same password
new_password1_id.text = new_password2_id.text = "password"
# before clicking the create account button,
# only the main thread is running
self.assertEqual(len(threading.enumerate()), 1)
main_thread = threading.enumerate()[0]
self.assertEqual(type(main_thread), threading._MainThread)
# click the create account button
create_account_button_id.dispatch('on_release')
# currently account creation do not run in a thread, so only 1 thread
self.assertEqual(len(threading.enumerate()), 1)
self.assertEqual(type(main_thread), threading._MainThread)
"""
create_account_thread = threading.enumerate()[0]
self.assertEqual(
create_account_thread._Thread__target.func_name,
"create_account")
# waits for the end of the thread
create_account_thread.join()
# thread has ended and the main thread is running alone again
self.assertEqual(len(threading.enumerate()), 1)
main_thread = threading.enumerate()[0]
self.assertEqual(type(main_thread), threading._MainThread)
"""
# and verifies the account was created
self.assertEqual(len(account_utils.get_account_list()), 1)
self.assertEqual(new_password1_id.text, '')
self.assertEqual(new_password2_id.text, '')
# we should get redirected to the overview page
self.advance_frames_for_screen()
self.assertEqual(controller.screen_manager.current, 'roll_screen')
# the new account should be loaded in the controller
self.assertEqual(
controller.switch_account_screen.current_account,
account_utils.get_account_list()[0])
# joins ongoing threads
[t.join() for t in threading.enumerate()[1:]]
# check the redirect dialog
dialogs = Dialog.dialogs
self.assertEqual(len(dialogs), 1)
dialog = dialogs[0]
self.assertEqual(dialog.title, 'Account created, redirecting...')
dialog.dismiss()
self.assertEqual(len(dialogs), 0)

# main test function
def run_test(self, app, *args):
Expand All @@ -144,6 +230,7 @@ def run_test(self, app, *args):
self.helper_test_empty_account(app)
self.helper_test_toolbar(app)
self.helper_test_about_screen(app)
self.helper_test_create_first_account(app)
# Comment out if you are editing the test, it'll leave the
# Window opened.
app.stop()
Expand Down

0 comments on commit 1ac5a75

Please sign in to comment.