Skip to content

Commit 9ddfe31

Browse files
authored
Merge pull request #102 from FreakJoe/master
Localization and restructured tests
2 parents e7278a3 + 3f7f5fe commit 9ddfe31

28 files changed

+1657
-138
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
recursive-include rein/html *
22
recursive-include rein/lib/crypto/resources *
3+
recursive-include rein/locale *

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ clean:
33
rm -f enrollment.txt.sig
44

55
test:
6-
python -m unittest2 rein/lib/script.py
7-
python -m unittest2 rein/lib/bitcoinaddress.py
6+
nosetests -v --ignore-files="test_cli.py"
7+
8+
test_all:
9+
nosetests -v

README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,27 +80,40 @@ For the mediator payment, a mandatory multisig address is created. To spend the
8080

8181
Like with the primary payment, a user is prompted for a signed mediator payment if they are a job creator accepting a delivery or they are the mediator resolving a dispute.
8282

83-
## Development and Testing
83+
## Developer Notes
8484

8585
We have [a quick Vagrant-based setup](https://github.com/ReinProject/devsetup) that gives you a virtual machine with the python-rein client, Causway server and its Bitcoin Core (testnet) node all configured to work together. Testing usually involves creating users and walking through jobs so a virtual machine that has all components going, even allowing payments to be sent is very helpful.
8686

87-
Tests are run using nose:
87+
To generate or update pot files for translation, run the following from the root of the repo:
8888

89-
$ nosetests
89+
xgettext.pl rein/cli.py rein/lib/*.py
90+
91+
## Testing
92+
93+
We have [a quick Vagrant-based setup](https://github.com/ReinProject/devsetup) that gives you a virtual machine with the python-rein client, Causway server and its Bitcoin Core (testnet) node all configured to work together. Testing usually involves creating users and walking through jobs so a virtual machine that has all components going, even allowing payments to be sent is very helpful.
94+
95+
Tests are run using nose, with the make file specifying two different test commands:
96+
97+
$ make test
98+
nosetests -v --ignore-files="test_cli.py"
9099
..
91100
----------------------------------------------------------------------
92-
Ran 2 tests in 0.837s
101+
Ran 5 tests in 0.344
93102

94103
OK
95104

96-
And unittest2:
105+
and
97106

98-
$ make test
99-
python -m unittest2 rein/lib/*.py
100-
....
107+
$ make test_all
108+
nosetests -v
109+
..
101110
----------------------------------------------------------------------
102-
Ran 4 tests in 0.001s
103-
111+
Ran 7 tests in 2.393s
112+
104113
OK
105114

115+
By default, the more limited "test" command should be used when a dedicated test-environment, such as the Vagrant set-up mentioned above, is not available. It currently runs all tests except the tests specified in "tests/test_cli.py".
116+
106117
Tox fails right now but does run flake so will be helpful for cleanup.
118+
119+
Be aware that new unit tests should be added to a file within the tests directory. Both the file name and the test method names should follow the naming convention "test_*".

rein/cli.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .lib.util import unique
2727
from .lib.io import safe_get
2828
from .lib.script import build_2_of_3, build_mandatory_multisig, check_redeem_scripts
29+
from .lib.localization import init_localization
2930
from .lib.transaction import partial_spend_p2sh, spend_p2sh, spend_p2sh_mediator, partial_spend_p2sh_mediator, partial_spend_p2sh_mediator_2
3031
from .lib.rating import add_rating, get_user_jobs
3132

@@ -45,17 +46,20 @@
4546
from .lib.mediator import Mediator
4647

4748
rein = config.Config()
49+
init_localization()
50+
4851
import bitcoin
4952
from bitcoin.wallet import P2PKHBitcoinAddress
5053
from bitcoin.core import x
5154
if (rein.testnet): bitcoin.SelectParams('testnet')
55+
init_localization()
5256

5357

5458
@click.group()
5559
@click.option('--debug/--no-debug', default=False)
5660
@click.pass_context
5761
def cli(ctx, debug):
58-
"""
62+
_("""
5963
Rein is a decentralized professional services market and Python-rein is a client
6064
that provides a user interface. Use this program from your local browser or command
6165
line to create an account, post a job, bid, etc.
@@ -79,7 +83,7 @@ def cli(ctx, debug):
7983
$ rein resolve - mediator posts decision
8084
8185
For more info and the setup guide visit: http://reinproject.org
82-
"""
86+
""")
8387
if debug:
8488
click.echo("Debuggin'")
8589
pass
@@ -88,23 +92,24 @@ def cli(ctx, debug):
8892
@cli.command()
8993
@click.option('--multi/--no-multi', default=False, help="add even if an identity exists")
9094
def setup(multi):
91-
"""
95+
_("""
9296
Setup or import an identity.
9397
9498
You will choose a name or handle for your account, include public contact information,
9599
and a delegate Bitcoin address/private key that the program will use to sign documents
96100
on your behalf. An enrollment document will be created and you will need to sign it
97101
with your master Bitcoin private key.
98-
"""
102+
""")
99103
log = rein.get_log()
100104
if multi:
101105
rein.set_multiuser()
102106
log.info('entering setup')
103107
if multi or rein.has_no_account():
104-
click.echo("\n" + highlight("Welcome to Rein.", True, True) + "\n\n"
105-
"Do you want to import a backup or create a new account?\n\n"
106-
"1 - Create new account\n2 - Import backup\n")
108+
click.echo("\n" + highlight(_("Welcome to Rein."), True, True) + "\n\n" +
109+
_("Do you want to import a backup or create a new account?\n\n") +
110+
_("1 - Create new account\n2 - Import backup\n"))
107111
choice = click.prompt(highlight("Choice", True, True), type=int, default=1)
112+
108113
if choice == 1:
109114
create_account(rein)
110115
log.info('account created')

rein/lib/bitcoinaddress.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
from crypto.util import ripemd160
33
from bitcoin import base58
44
from binascii import unhexlify
5-
import unittest
65

76
digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
87

9-
108
def to_bytes(n, length, endianess='big'):
119
h = '%x' % n
1210
s = ('0' * (len(h) % 2) + h).zfill(length * 2).decode('hex')
@@ -26,16 +24,6 @@ def check_bitcoin_address(bc):
2624
bcbytes = decode_base58(bc, 25)
2725
return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]
2826

29-
30-
class BitcoinAddressTest(unittest.TestCase):
31-
def test_check_bitcoin_address(self):
32-
self.assertTrue(check_bitcoin_address('1CptxARjqcfkVwGFSjR82zmPT8YtRMubub'))
33-
self.assertTrue(check_bitcoin_address('3746f7fjJ6fG1pQXDjA8xy9WAzf4968WWv'))
34-
self.assertFalse(check_bitcoin_address('2746f7fjJ6fG1pQXDjA8xy9WAzf4968WWv'))
35-
36-
def test_check_sin(self):
37-
self.assertEqual(generate_sin('02F840A04114081690223B7069071A70D6DABB891763B638CC20C7EC3BD58E6C86'), 'TfG4ScDgysrSpodWD4Re5UtXmcLbY5CiUHA')
38-
3927
def generate_sin(master_key):
4028
"""Generates a type 2 'Secure Identity Number' using the bip32 master public key"""
4129
prefix = 0x0F

rein/lib/forms.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616

1717
def validate_privkey(form, field):
1818
if not privkey_to_address(field.data):
19-
raise ValidationError("Not a valid private key.")
19+
raise ValidationError(_("Not a valid private key."))
2020

2121
def validate_address(form, field):
2222
if not check_bitcoin_address(field.data):
23-
raise ValidationError("Invalid address")
23+
raise ValidationError(_("Invalid address"))
2424

2525
def validate_en(form, field):
2626
message = field.data.replace("\r\n","\n")
2727
if not validate_enrollment(message):
28-
raise ValidationError("Invalid signature")
28+
raise ValidationError(_("Invalid signature"))
2929

3030
def validate_mediator_fee(form, field):
3131
try:
@@ -38,55 +38,55 @@ def avoid_self_rating(form, field):
3838
raise ValidationError('You may not rate yourself!')
3939

4040
class SetupForm(Form):
41-
name = TextField('Name / Handle', validators = [Required()])
42-
contact = TextField('Email / Bitmessage', validators = [Required()])
43-
maddr = TextField('Master Bitcoin address', validators = [Required(), validate_address])
44-
daddr = TextField('Delegate Bitcoin address', validators = [Required(), validate_address])
45-
dkey = PasswordField('Delegate Bitcoin private Key', validators = [Required(), validate_privkey])
46-
will_mediate = RadioField('Register as a mediator?', choices = [('1','Yes'), ('0', 'No')])
47-
mediator_fee = TextField('Mediator Fee', validators = [validate_mediator_fee]) # TODO make required only if Yes above
41+
name = TextField(_('Name / Handle'), validators = [Required()])
42+
contact = TextField(_('Email / Bitmessage'), validators = [Required()])
43+
maddr = TextField(_('Master Bitcoin address'), validators = [Required(), validate_address])
44+
daddr = TextField(_('Delegate Bitcoin address'), validators = [Required(), validate_address])
45+
dkey = PasswordField(_('Delegate Bitcoin private Key'), validators = [Required(), validate_privkey])
46+
will_mediate = RadioField(_('Register as a mediator?'), choices = [('1','Yes'), ('0', 'No')])
47+
mediator_fee = TextField(_('Mediator Fee'), validators = [validate_mediator_fee]) # TODO make required only if Yes above
4848

4949
class SignForm(Form):
5050
identity_id = HiddenInput("identity_id")
51-
signed = TextAreaField('Signed enrollment', validators = [Required(), validate_en])
51+
signed = TextAreaField(_('Signed enrollment'), validators = [Required(), validate_en])
5252

5353
class JobPostForm(Form):
54-
job_name = TextField('Job name', validators = [Required()])
55-
description = TextAreaField('Description', validators = [Required()])
56-
tags = TextField('Tags', validators = [Required()])
57-
expire_days = TextField('Expiration (days)', validators = [Required()])
58-
mediator_maddr = RadioField('Choose mediator')
54+
job_name = TextField(_('Job name'), validators = [Required()])
55+
description = TextAreaField(_('Description'), validators = [Required()])
56+
tags = TextField(_('Tags'), validators = [Required()])
57+
expire_days = TextField(_('Expiration (days)'), validators = [Required()])
58+
mediator_maddr = RadioField(_('Choose mediator'))
5959

6060
class BidForm(Form):
61-
description = TextAreaField('Description', validators = [Required()])
62-
bid_amount = TextAreaField('Bid amount', validators = [Required()])
63-
job_id = RadioField('Choose Job to bid on')
61+
description = TextAreaField(_('Description'), validators = [Required()])
62+
bid_amount = TextAreaField(_('Bid amount'), validators = [Required()])
63+
job_id = RadioField(_('Choose Job to bid on'))
6464

6565
class JobOfferForm(Form):
66-
bid_id = RadioField('Choose bid')
66+
bid_id = RadioField(_('Choose bid'))
6767

6868
class DeliverForm(Form):
69-
deliverable = TextAreaField('Deliverables', validators = [Required()])
70-
job_id = RadioField('Choose job associated with deliverables')
69+
deliverable = TextAreaField(_('Deliverables'), validators = [Required()])
70+
job_id = RadioField(_('Choose job associated with deliverables'))
7171

7272
class DisputeForm(Form):
73-
dispute_detail = TextAreaField('Dispute detail', validators = [Required()])
74-
order_id = RadioField('Choose job')
73+
dispute_detail = TextAreaField(_('Dispute detail'), validators = [Required()])
74+
order_id = RadioField(_('Choose job'))
7575

7676
class AcceptForm(Form):
77-
deliverable_id = RadioField('Deliverables')
77+
deliverable_id = RadioField(_('Deliverables'))
7878

7979
class ResolveForm(Form):
80-
resolution = TextAreaField('Resolution', validators = [Required()])
81-
client_payment_amount = TextField('Client payment amount in BTC (remainder sent to worker)', validators = [Required()])
82-
dispute_id = RadioField('Disputes')
80+
resolution = TextAreaField(_('Resolution'), validators = [Required()])
81+
client_payment_amount = TextField(_('Client payment amount in BTC (remainder sent to worker)'), validators = [Required()])
82+
dispute_id = RadioField(_('Disputes'))
8383

8484
class AcceptResolutionForm(Form):
85-
resolution_id = RadioField('Resolution')
85+
resolution_id = RadioField(_('Resolution'))
8686

8787
class RatingForm(Form):
88-
job_id = TextField('Job id', validators = [Required()], default='')
89-
user_id = TextField('User SIN', validators = [Required(), avoid_self_rating], default='')
90-
rated_by_id = TextField('Rated by SIN', validators = [Required()], default='')
91-
rating = TextField('Rating', validators=[Required()], default=0, widget=HiddenInput())
92-
comments = TextAreaField('Comments', validators = [], default='')
88+
job_id = TextField(_('Job ID'), validators = [Required()], default='')
89+
user_id = TextField(_('User SIN'), validators = [Required(), avoid_self_rating], default='')
90+
rated_by_id = TextField(_('Rated by SIN'), validators = [Required()], default='')
91+
rating = TextField(_('Rating'), validators=[Required()], default=0, widget=HiddenInput())
92+
comments = TextAreaField(_('Comments'), validators = [], default='')

rein/lib/io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def safe_get(log, url):
1313
try:
1414
answer = requests.get(url=url, proxies=rein.proxies)
1515
except requests.ConnectionError:
16-
click.echo('Error connecting to server.')
16+
click.echo(_('Error connecting to server.'))
1717
log.error('server connect error ' + url)
1818
return None
1919

rein/lib/localization.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import gettext
2+
import locale
3+
import logging
4+
import os
5+
import rein.locale
6+
7+
def init_localization():
8+
'''prepare l10n'''
9+
# Extract system locale identifier
10+
loc = locale.getdefaultlocale()[0]
11+
file_name = "messages-%s.mo" % loc.split('_')[0].upper()
12+
file_path = os.path.join(os.path.dirname(os.path.abspath(rein.locale.__file__)), file_name)
13+
14+
try:
15+
logging.debug("Opening message file %s for locale %s", file_name, loc[0])
16+
trans = gettext.GNUTranslations(open(file_path, "rb" ))
17+
18+
except IOError:
19+
logging.debug("Locale not found. Using default messages")
20+
trans = gettext.NullTranslations()
21+
22+
trans.install()
23+
24+
if __name__ == '__main__':
25+
init_localization()
26+

rein/lib/market.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def sign_and_store_document(rein, doc_type, document, signature_address=None, si
5353
click.echo("\n%s\n" % document)
5454
done = False
5555
while not done:
56-
filename = click.prompt("File containing signed document", type=str, default=doc_type + '.sig.txt')
56+
filename = click.prompt(_("File containing signed document"), type=str, default=doc_type + '.sig.txt')
5757
if os.path.isfile(filename):
5858
done = True
5959
f = open(filename, 'r')

0 commit comments

Comments
 (0)