Browse Source

convert tests to be data driven, use pathlib, fix creating metadata

update print order to print common fields first per order in list..

if ident doesn't change it (no args), print out current identity..
main
John-Mark Gurney 2 years ago
parent
commit
0f2dd2a693
2 changed files with 115 additions and 101 deletions
  1. +1
    -1
      ui/Makefile
  2. +114
    -100
      ui/medashare/cli.py

+ 1
- 1
ui/Makefile View File

@@ -3,7 +3,7 @@ MODULES=medashare

test: fixtures/sample.data.pasn1 fixtures/sample.persona.pasn1 fixtures/sample.mtree
(. ./p/bin/activate && \
(find $(MODULES) -type f | entr sh -c 'python3 -m coverage run -m unittest --failfast $(MODULES).tests && coverage report -m --omit=p/\*'))
((find fixtures -type f; find $(MODULES) -type f) | entr sh -c 'python3 -m coverage run -m unittest --failfast $(MODULES).tests && coverage report -m --omit=p/\*'))

env:
$(VIRTUALENV) p


+ 114
- 100
ui/medashare/cli.py View File

@@ -7,14 +7,19 @@ from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey, \
from cryptography.hazmat.primitives.serialization import Encoding, \
PrivateFormat, PublicFormat, NoEncryption

from unittest import mock

import base58
import copy
import datetime
import functools
import hashlib
from unittest import mock
import io
import json
import os.path
import pathlib
import pasn1
import re
import shutil
import string
import sys
@@ -29,12 +34,18 @@ _defaulthash = 'sha512'
_validhashes = set([ 'sha256', 'sha512' ])
_hashlengths = { len(getattr(hashlib, x)().hexdigest()): x for x in _validhashes }

def _iterdictlist(obj):
for k, v in sorted(obj.items()):
def _keyordering(x):
k, v = x
try:
return (MDBase._common_names_list.index(k), k, v)
except ValueError:
return (2**32, k, v)

def _iterdictlist(obj, **kwargs):
l = list(sorted(obj.items(**kwargs), key=_keyordering))
for k, v in l:
if isinstance(v, list):
v = v[:]
v.sort()
for i in v:
for i in sorted(v):
yield k, i
else:
yield k, v
@@ -65,11 +76,13 @@ class MDBase(object):
_instance_properties = {
'uuid': _makeuuid,
'created_by_ref': _makeuuid,
#'parent_refs': lambda x: [ _makeuuid(y) for y in x ],
}

_common_properties = [ 'type', 'created_by_ref' ] # XXX - add lang?
_common_optional = set(('parent_refs', 'sig'))
_common_names = set(_common_properties + list(_generated_properties.keys()))
_common_names_list = _common_properties + list(_generated_properties.keys())

def __init__(self, obj={}, **kwargs):
obj = copy.deepcopy(obj)
@@ -573,9 +586,14 @@ def cmd_ident(options):

persona = Persona.load(identfname)

persona.new_version(*(x.split('=', 1) for x in options.tagvalue))
if options.tagvalue:
persona.new_version(*(x.split('=', 1) for x in options.tagvalue))

persona.store(identfname)
persona.store(identfname)
else:
ident = persona.get_identity()
for k, v in _iterdictlist(ident, skipcommon=False):
print('%s:\t%s' % (k, v))

def cmd_pubkey(options):
identfname = os.path.expanduser('~/.medashare_identity.pasn1')
@@ -589,12 +607,12 @@ def cmd_modify(options):

props = [[ x[0] ] + x[1:].split('=', 1) for x in options.modtagvalues]
if any(x[0] not in ('+', '-') for x in props):
print('ERROR: tag needs to start w/ a "+" (add) or a "-" (remove).', file=sys.stderr)
print('ERROR: tag needs to start with a "+" (add) or a "-" (remove).', file=sys.stderr)
sys.exit(1)

badtags = list(x[1] for x in props if x[1] in (MDBase._common_names | MDBase._common_optional))
if any(badtags):
print('ERROR: invalid tag(s): %s.' % repr(badtags), file=sys.stderr)
print('ERROR: invalid tag%s: %s.' % ( 's' if len(badtags) > 1 else '', repr(badtags)), file=sys.stderr)
sys.exit(1)

adds = [ x[1:] for x in props if x[0] == '+' ]
@@ -606,11 +624,13 @@ def cmd_modify(options):
dels = [ x[1:] for x in props if x[0] == '-' ]

for i in options.files:
# Get MetaData
try:
objs = objstr.by_file(i)
except KeyError:
fobj = objstr
objs = [ persona.by_file(i) ]
fobj = persona.by_file(i)
objstr.loadobj(fobj)
objs = [ persona.MetaData(hashes=fobj.hashes) ]

for j in objs:
# make into key/values
@@ -646,6 +666,7 @@ def cmd_list(options):
for k, v in _iterdictlist(j):
print('%s:\t%s' % (k, v))
except (KeyError, FileNotFoundError):
# XXX - tell the difference?
print('ERROR: file not found: %s' % repr(i), file=sys.stderr)
sys.exit(1)

@@ -665,7 +686,7 @@ def main():
parser_gi.set_defaults(func=cmd_genident)

parser_i = subparsers.add_parser('ident', help='update identity')
parser_i.add_argument('tagvalue', nargs='+',
parser_i.add_argument('tagvalue', nargs='*',
help='add the arg as metadata for the identity, tag=[value]')
parser_i.set_defaults(func=cmd_ident)

@@ -717,20 +738,25 @@ class _TestCononicalCoder(unittest.TestCase):

class _TestCases(unittest.TestCase):
def setUp(self):
d = os.path.realpath(tempfile.mkdtemp())
self.fixtures = pathlib.Path('fixtures').resolve()

d = pathlib.Path(tempfile.mkdtemp()).resolve()
self.basetempdir = d
self.tempdir = os.path.join(d, 'subdir')
self.tempdir = d / 'subdir'

persona = Persona.load(os.path.join('fixtures', 'sample.persona.pasn1'))
self.created_by_ref = persona.get_identity().uuid

shutil.copytree(os.path.join('fixtures', 'testfiles'),
self.tempdir)
shutil.copytree(self.fixtures / 'testfiles', self.tempdir)

self.oldcwd = os.getcwd()

def tearDown(self):
shutil.rmtree(self.basetempdir)
self.tempdir = None

os.chdir(self.oldcwd)

def test_mdbase(self):
self.assertRaises(ValueError, MDBase, created_by_ref='')
self.assertRaises(ValueError, MDBase.create_obj, { 'type': 'unknosldkfj' })
@@ -827,7 +853,7 @@ class _TestCases(unittest.TestCase):

oldid = ftest.id
self.assertEqual(ftest.filename, fname)
self.assertEqual(ftest.dir, self.tempdir)
self.assertEqual(ftest.dir, str(self.tempdir))
# XXX - do we add host information?
self.assertEqual(ftest.id, uuid.uuid5(_NAMESPACE_MEDASHARE_PATH,
'/'.join(os.path.split(self.tempdir) +
@@ -983,6 +1009,10 @@ class _TestCases(unittest.TestCase):
# and that it can be verified
persona.verify(mdobj)

# that when round tripped through pasn1.
a = mdobj.encode()
b = MDBase.decode(a)

def test_objectstore(self):
objst = ObjectStore.load(os.path.join('fixtures', 'sample.data.pasn1'))

@@ -1036,6 +1066,70 @@ class _TestCases(unittest.TestCase):
# Tests to add:
# Non-duplicates when same metadata is located by multiple hashes.

def run_command_file(self, f):
with open(f) as fp:
cmds = json.load(fp)

# setup object store
storefname = self.tempdir / 'storefname'
identfname = self.tempdir / 'identfname'

# setup path mapping
def expandusermock(arg):
if arg == '~/.medashare_store.pasn1':
return storefname
elif arg == '~/.medashare_identity.pasn1':
return identfname

# setup test fname
testfname = os.path.join(self.tempdir, 'test.txt')
newtestfname = os.path.join(self.tempdir, 'newfile.txt')

for cmd in cmds:
try:
special = cmd['special']
except KeyError:
pass
else:
if special == 'copy newfile.txt to test.txt':
shutil.copy(newtestfname, testfname)
elif special == 'change newfile.txt':
with open(newtestfname, 'w') as fp:
fp.write('some new contents')

continue

with self.subTest(file=f, title=cmd['title']), \
mock.patch('os.path.expanduser',
side_effect=expandusermock) as eu, \
mock.patch('sys.stdout', io.StringIO()) as stdout, \
mock.patch('sys.stderr', io.StringIO()) as stderr, \
mock.patch('sys.argv', [ 'progname', ] +
cmd['cmd']) as argv:
with self.assertRaises(SystemExit) as cm:
main()

# XXX - Minor hack till other tests fixed
sys.exit(0)

# with the correct output
self.maxDiff = None
outre = cmd.get('stdout_re')
if outre:
self.assertRegex(stdout.getvalue(), outre)
else:
self.assertEqual(stdout.getvalue(), cmd.get('stdout', ''))
self.assertEqual(stderr.getvalue(), cmd.get('stderr', ''))

self.assertEqual(cm.exception.code, cmd.get('exit', 0))

def test_cmds(self):
cmds = self.fixtures.glob('cmd.*.json')

for i in cmds:
os.chdir(self.tempdir)
self.run_command_file(i)

def test_main(self):
# Test the main runner, this is only testing things that are
# specific to running the program, like where the store is
@@ -1044,6 +1138,8 @@ class _TestCases(unittest.TestCase):
# setup object store
storefname = os.path.join(self.tempdir, 'storefname')
identfname = os.path.join(self.tempdir, 'identfname')

# XXX part of the problem
shutil.copy(os.path.join('fixtures', 'sample.data.pasn1'), storefname)

# setup path mapping
@@ -1057,7 +1153,6 @@ class _TestCases(unittest.TestCase):
testfname = os.path.join(self.tempdir, 'test.txt')
newtestfname = os.path.join(self.tempdir, 'newfile.txt')

import io
import itertools

real_stderr = sys.stderr
@@ -1139,87 +1234,6 @@ class _TestCases(unittest.TestCase):
# and that the old Persona can verify the new one
self.assertTrue(persona.verify(nident))

# that when asked to print the public key
with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'pubkey' ]) as argv:
main()

# the correct key is printed
self.assertEqual(stdout.getvalue(),
'%s\n' % persona.get_pubkey().decode('ascii'))

# and looked up the correct file
eu.assert_called_with('~/.medashare_identity.pasn1')

# that when a new file is printed
with mock.patch('sys.stderr', io.StringIO()) as stderr, mock.patch('sys.argv', [ 'progname', 'list', newtestfname ]) as argv:
# that it exits
with self.assertRaises(SystemExit) as cm:
main()

# with error code 1
self.assertEqual(cm.exception.code, 1)

# and outputs an error message
self.assertEqual(stderr.getvalue(),
'ERROR: file not found: %s\n' % repr(newtestfname))

# that when a tag is incomplete
with mock.patch('sys.stderr', io.StringIO()) as stderr, mock.patch('sys.argv', [ 'progname', 'modify', '+tag', newtestfname ]) as argv:
# that it exits
with self.assertRaises(SystemExit) as cm:
main()

# with error code 1
self.assertEqual(cm.exception.code, 1)

# and outputs an error message
self.assertEqual(stderr.getvalue(),
'ERROR: invalid tag, needs an "=".\n')

# that when a new file has a tag added
with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'modify', '+tag=', newtestfname ]) as argv:
main()

# nothing is printed
self.assertEqual(stdout.getvalue(), '');

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'list', testfname ]) as argv:
main()
self.assertEqual(stdout.getvalue(),
'dc:creator:\tJohn-Mark Gurney\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n')

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'modify', '+dc:creator=Another user', '+foo=bar=baz', testfname ]) as argv:
main()

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'list', testfname ]) as argv:
main()
self.assertEqual(stdout.getvalue(),
'dc:creator:\tAnother user\ndc:creator:\tJohn-Mark Gurney\nfoo:\tbar=baz\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n')

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'modify', '-dc:creator', testfname ]) as argv:
main()

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'list', testfname ]) as argv:
main()
self.assertEqual(stdout.getvalue(),
'foo:\tbar=baz\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n')

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'modify', '+foo=bleh', testfname ]) as argv:
main()

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'list', testfname ]) as argv:
main()
self.assertEqual(stdout.getvalue(),
'foo:\tbar=baz\nfoo:\tbleh\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n')

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'modify', '-foo=bar=baz', testfname ]) as argv:
main()

with mock.patch('sys.stdout', io.StringIO()) as stdout, mock.patch('sys.argv', [ 'progname', 'list', testfname ]) as argv:
main()
self.assertEqual(stdout.getvalue(),
'foo:\tbleh\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n')

orig_open = open
with mock.patch('os.path.expanduser', side_effect=expandusermock) \
as eu, mock.patch('medashare.cli.open') as op:


Loading…
Cancel
Save