db_tests.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. #============================================================================
  2. # This file is part of Pwman3.
  3. #
  4. # Pwman3 is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License, version 2
  6. # as published by the Free Software Foundation;
  7. #
  8. # Pwman3 is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with Pwman3; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. #============================================================================
  17. # Copyright (C) 2013 Oz Nahum <nahumoz@gmail.com>
  18. #============================================================================
  19. from pwman.data.nodes import NewNode
  20. from pwman.data.tags import TagNew
  21. from pwman.data import factory
  22. from pwman.data.drivers.sqlite import DatabaseException, SQLiteDatabaseNewForm
  23. from pwman.util import config
  24. from pwman.util.config import get_pass_conf
  25. from pwman.util.generator import leetlist
  26. from pwman.util.crypto import CryptoEngine, CryptoBadKeyException
  27. from pwman import default_config, set_xsel
  28. from pwman.ui import get_ui_platform
  29. from pwman.ui.tools import CMDLoop, CliMenuItem
  30. from pwman import (parser_options, get_conf_options, get_conf_file, set_umask)
  31. from pwman.data.database import __DB_FORMAT__
  32. from pwman.ui.mac import PwmanCliMacNew
  33. from pwman.ui.win import PwmanCliWinNew
  34. from collections import namedtuple
  35. import unittest
  36. import StringIO
  37. import os
  38. import os.path
  39. import sys
  40. dummyfile = """
  41. [Encryption]
  42. [Readline]
  43. [Global]
  44. xsel = /usr/bin/xsel
  45. colors = yes
  46. umask = 0100
  47. cls_timeout = 5
  48. [Database]
  49. """
  50. def node_factory(username, password, url, notes, tags=None):
  51. node = NewNode()
  52. node.username = username
  53. node.password = password
  54. node.url = url
  55. node.notes = notes
  56. tags = [TagNew(tn) for tn in tags]
  57. node.tags = tags
  58. return node
  59. _saveconfig = False
  60. PwmanCliNew, OSX = get_ui_platform(sys.platform)
  61. from test_tools import (SetupTester, DummyCallback2,
  62. DummyCallback3, DummyCallback4)
  63. class DBTests(unittest.TestCase):
  64. """test everything related to db"""
  65. def setUp(self):
  66. "test that the right db instance was created"
  67. dbver = __DB_FORMAT__
  68. self.dbtype = config.get_value("Database", "type")
  69. self.db = factory.create(self.dbtype, dbver)
  70. self.tester = SetupTester(dbver)
  71. self.tester.create()
  72. def test_db_created(self):
  73. "test that the right db instance was created"
  74. self.assertIn(self.dbtype, self.db.__class__.__name__)
  75. def test_db_opened(self):
  76. "db was successfuly opened"
  77. # it will have a file name associated
  78. self.assertTrue(hasattr(self.db, '_filename'))
  79. def test_create_node(self):
  80. "test that a node can be successfuly created"
  81. # this method does not test do_new
  82. # which is a UI method, rather we test
  83. # _db.addnodes
  84. username = 'tester'
  85. password = 'Password'
  86. url = 'example.org'
  87. notes = 'some notes'
  88. #node = NewNode(username, password, url, notes)
  89. node = NewNode()
  90. node.username = username
  91. node.password = password
  92. node.url = url
  93. node.notes = notes
  94. #node = NewNode(username, password, url, notes)
  95. tags = [TagNew(tn) for tn in ['testing1', 'testing2']]
  96. node.tags = tags
  97. self.db.open()
  98. self.db.addnodes([node])
  99. idx_created = node._id
  100. new_node = self.db.getnodes([idx_created])[0]
  101. for key, attr in {'password': password, 'username': username,
  102. 'url': url, 'notes': notes}.iteritems():
  103. self.assertEquals(attr, getattr(new_node, key))
  104. self.db.close()
  105. def test_tags(self):
  106. enc = CryptoEngine.get()
  107. got_tags = self.tester.cli._tags(enc)
  108. self.assertEqual(2, len(got_tags))
  109. def test_change_pass(self):
  110. enc = CryptoEngine.get()
  111. enc._callback = DummyCallback2()
  112. self.assertRaises(CryptoBadKeyException,
  113. self.tester.cli._db.changepassword)
  114. def test_db_change_pass(self):
  115. "fuck yeah, we change the password and the new dummy works"
  116. enc = CryptoEngine.get()
  117. enc._callback = DummyCallback3()
  118. self.tester.cli._db.changepassword()
  119. self.tester.cli.do_forget('')
  120. enc._callback = DummyCallback4()
  121. self.tester.cli.do_ls('')
  122. def test_db_list_tags(self):
  123. # tags are return as ecrypted strings
  124. tags = self.tester.cli._db.listtags()
  125. self.assertEqual(2, len(tags))
  126. self.tester.cli.do_filter('testing1')
  127. tags = self.tester.cli._db.listtags()
  128. self.assertEqual(2, len(tags))
  129. self.tester.cli.do_ls('')
  130. def test_db_remove_node(self):
  131. node = self.tester.cli._db.getnodes([1])
  132. self.tester.cli._db.removenodes(node)
  133. # create the removed node again
  134. node = NewNode()
  135. node.username = 'tester'
  136. node.password = 'Password'
  137. node.url = 'example.org'
  138. node.notes = 'some notes'
  139. tags = [TagNew(tn) for tn in ['testing1', 'testing2']]
  140. node.tags = tags
  141. self.db.open()
  142. self.db.addnodes([node])
  143. def test_sqlite_init(self):
  144. db = SQLiteDatabaseNewForm("test")
  145. self.assertEquals("test", db._filename)
  146. class TestDBFalseConfig(unittest.TestCase):
  147. def setUp(self):
  148. #filename = default_config['Database'].pop('filename')
  149. self.fname1 = default_config['Database'].pop('filename')
  150. self.fname = config._conf['Database'].pop('filename')
  151. def test_db_missing_conf_parameter(self):
  152. self.assertRaises(DatabaseException, factory.create,
  153. 'SQLite', __DB_FORMAT__)
  154. def test_get_ui_platform(self):
  155. uiclass, osx = get_ui_platform('windows')
  156. self.assertFalse(osx)
  157. self.assertEqual(uiclass.__name__, PwmanCliWinNew.__name__)
  158. uiclass, osx = get_ui_platform('darwin')
  159. self.assertTrue(osx)
  160. self.assertEqual(uiclass.__name__, PwmanCliMacNew.__name__)
  161. del(uiclass)
  162. del(osx)
  163. def tearDown(self):
  164. config.set_value('Database', 'filename', self.fname)
  165. default_config['Database']['filename'] = self.fname1
  166. config._conf['Database']['filename'] = self.fname
  167. class CLITests(unittest.TestCase):
  168. """
  169. test command line functionallity
  170. """
  171. def setUp(self):
  172. "test that the right db instance was created"
  173. self.dbtype = config.get_value("Database", "type")
  174. self.db = factory.create(self.dbtype, __DB_FORMAT__)
  175. self.tester = SetupTester(__DB_FORMAT__)
  176. self.tester.create()
  177. def test_input(self):
  178. name = self.tester.cli.get_username(reader=lambda: u'alice')
  179. self.assertEqual(name, u'alice')
  180. def test_password(self):
  181. password = self.tester.cli.get_password(None,
  182. reader=lambda x: u'hatman')
  183. self.assertEqual(password, u'hatman')
  184. def test_random_password(self):
  185. password = self.tester.cli.get_password(None, length=7)
  186. self.assertEqual(len(password), 7)
  187. def test_random_leet_password(self):
  188. password = self.tester.cli.get_password(None, leetify=True, length=7)
  189. l_num = 0
  190. for v in leetlist.values():
  191. if v in password:
  192. l_num += 1
  193. # sometime despite all efforts, randomness dictates that no
  194. # leetifying happens ...
  195. self.assertTrue(l_num >= 0)
  196. def test_leet_password(self):
  197. password = self.tester.cli.get_password(None, leetify=True,
  198. reader=lambda x: u'HAtman')
  199. self.assertRegexpMatches(password, ("(H|h)?(A|a|4)?(T|t|\+)?(m|M|\|"
  200. "\/\|)?(A|a|4)?(N|n|\|\\|)?"))
  201. def test_get_url(self):
  202. url = self.tester.cli.get_url(reader=lambda: u'example.com')
  203. self.assertEqual(url, u'example.com')
  204. def test_get_notes(self):
  205. notes = self.tester.cli.get_notes(reader=lambda:
  206. u'test 123\n test 456')
  207. self.assertEqual(notes, u'test 123\n test 456')
  208. def test_get_tags(self):
  209. tags = self.tester.cli.get_tags(reader=lambda: u'looking glass')
  210. for t in tags:
  211. self.assertIsInstance(t, TagNew)
  212. for t, n in zip(tags, 'looking glass'.split()):
  213. self.assertEqual(t.name.strip(), n)
  214. # creating all the components of the node does
  215. # the node is still not added !
  216. def test_add_new_entry(self):
  217. # node = NewNode('alice', 'dough!', 'example.com',
  218. # 'lorem impsum')
  219. node = NewNode()
  220. node.username = 'alice'
  221. node.password = 'dough!'
  222. node.url = 'example.com'
  223. node.notes = 'somenotes'
  224. node.tags = 'lorem ipsum'
  225. tags = self.tester.cli.get_tags(reader=lambda: u'looking glass')
  226. node.tags = tags
  227. self.tester.cli._db.addnodes([node])
  228. self.tester.cli._db._cur.execute(
  229. "SELECT ID FROM NODES ORDER BY ID ASC", [])
  230. rows = self.tester.cli._db._cur.fetchall()
  231. # by now the db should have 2 new nodes
  232. # the first one was added by test_create_node in DBTests
  233. # the second was added just now.
  234. # This will pass only when running all the tests then ...
  235. self.assertEqual(len(rows), 2)
  236. node = NewNode()
  237. node.username = 'alice'
  238. node.password = 'dough!'
  239. node.url = 'example.com'
  240. node.notes = 'somenotes'
  241. node.tags = 'lorem ipsum'
  242. tags = self.tester.cli.get_tags(reader=lambda: u'looking glass')
  243. node.tags = tags
  244. self.tester.cli._db.addnodes([node])
  245. def test_get_ids(self):
  246. # used by do_cp or do_open,
  247. # this spits many time could not understand your input
  248. self.assertEqual([1], self.tester.cli.get_ids('1'))
  249. self.assertListEqual([1, 2, 3, 4, 5], self.tester.cli.get_ids('1-5'))
  250. self.assertListEqual([], self.tester.cli.get_ids('5-1'))
  251. self.assertListEqual([], self.tester.cli.get_ids('5x-1'))
  252. self.assertListEqual([], self.tester.cli.get_ids('5x'))
  253. self.assertListEqual([], self.tester.cli.get_ids('5\\'))
  254. def test_edit(self):
  255. node = self.tester.cli._db.getnodes([2])[0]
  256. menu = CMDLoop()
  257. menu.add(CliMenuItem("Username", self.tester.cli.get_username,
  258. node.username,
  259. node.username))
  260. menu.add(CliMenuItem("Password", self.tester.cli.get_password,
  261. node.password,
  262. node.password))
  263. menu.add(CliMenuItem("Url", self.tester.cli.get_url,
  264. node.url,
  265. node.url))
  266. menunotes = CliMenuItem("Notes",
  267. self.tester.cli.get_notes(reader=lambda:
  268. u'bla bla'),
  269. node.notes,
  270. node.notes)
  271. menu.add(menunotes)
  272. menu.add(CliMenuItem("Tags", self.tester.cli.get_tags,
  273. node.tags,
  274. node.tags))
  275. dummy_stdin = StringIO.StringIO('4\n\nX')
  276. self.assertTrue(len(dummy_stdin.readlines()))
  277. dummy_stdin.seek(0)
  278. sys.stdin = dummy_stdin
  279. menu.run(node)
  280. self.tester.cli._db.editnode(2, node)
  281. sys.stdin = sys.__stdin__
  282. def test_get_pass_conf(self):
  283. numerics, leet, s_chars = get_pass_conf()
  284. self.assertFalse(numerics)
  285. self.assertFalse(leet)
  286. self.assertFalse(s_chars)
  287. def test_do_tags(self):
  288. self.tester.cli.do_filter('bank')
  289. def test_do_forget(self):
  290. self.tester.cli.do_forget('')
  291. def test_do_auth(self):
  292. crypto = CryptoEngine.get()
  293. crypto.auth('12345')
  294. def test_do_clear(self):
  295. self.tester.cli.do_clear('')
  296. def test_do_exit(self):
  297. self.assertTrue(self.tester.cli.do_exit(''))
  298. class FakeSqlite(object):
  299. def check_db_version(self):
  300. return ""
  301. class FactoryTest(unittest.TestCase):
  302. def test_factory_check_db_ver(self):
  303. self.assertEquals(factory.check_db_version('SQLite'), 0.5)
  304. def test_factory_check_db_file(self):
  305. orig_sqlite = getattr(factory, 'sqlite')
  306. factory.sqlite = FakeSqlite()
  307. self.assertEquals(factory.check_db_version('SQLite'), 0.3)
  308. factory.sqlite = orig_sqlite
  309. def test_factory_create(self):
  310. db = factory.create('SQLite', filename='foo.db')
  311. db._open()
  312. self.assertTrue(os.path.exists('foo.db'))
  313. os.unlink('foo.db')
  314. self.assertIsInstance(db, SQLiteDatabaseNewForm)
  315. self.assertRaises(DatabaseException, factory.create, 'UNKNOWN')
  316. class ConfigTest(unittest.TestCase):
  317. def setUp(self):
  318. "test that the right db instance was created"
  319. dbver = 0.4
  320. self.dbtype = config.get_value("Database", "type")
  321. self.db = factory.create(self.dbtype, dbver)
  322. self.tester = SetupTester(dbver)
  323. self.tester.create()
  324. self.orig_config = config._conf.copy()
  325. self.orig_config['Encryption'] = {'algorithm': 'AES'}
  326. def test_config_write(self):
  327. _filename = os.path.join(os.path.dirname(__file__),
  328. 'testing_config')
  329. config._file = _filename
  330. config.save(_filename)
  331. self.assertTrue(_filename)
  332. os.remove(_filename)
  333. def test_config_write_with_none(self):
  334. _filename = os.path.join(os.path.dirname(__file__),
  335. 'testing_config')
  336. config._file = _filename
  337. config.save()
  338. self.assertTrue(os.path.exists(_filename))
  339. os.remove(_filename)
  340. def test_write_no_permission(self):
  341. # this test will pass if you run as root ...
  342. # assuming you are not doing something like that
  343. self.assertRaises(config.ConfigException, config.save,
  344. '/root/test_config')
  345. def test_add_default(self):
  346. config.add_defaults({'Section1': {'name': 'value'}})
  347. self.assertIn('Section1', config._defaults)
  348. config._defaults.pop('Section1')
  349. def test_get_conf(self):
  350. cnf = config.get_conf()
  351. cnf_keys = cnf.keys()
  352. self.assertTrue('Encryption' in cnf_keys)
  353. self.assertTrue('Readline' in cnf_keys)
  354. self.assertTrue('Global' in cnf_keys)
  355. self.assertTrue('Database' in cnf_keys)
  356. def test_load_conf(self):
  357. self.assertRaises(config.ConfigException, config.load, 'NoSuchFile')
  358. # Everything should be ok
  359. config.save('TestConfig.ini')
  360. config.load('TestConfig.ini')
  361. # let's corrupt the file
  362. cfg = open('TestConfig.ini', 'w')
  363. cfg.write('Corruption')
  364. cfg.close()
  365. self.assertRaises(config.ConfigException, config.load,
  366. 'TestConfig.ini')
  367. os.remove('TestConfig.ini')
  368. def test_all_config(self):
  369. sys.argv = ['pwman3']
  370. default_config['Database'] = {'type': '',
  371. 'filename': ''}
  372. _save_conf = config._conf.copy()
  373. config._conf = {}
  374. with open('dummy.conf', 'w') as dummy:
  375. dummy.write(dummyfile)
  376. sys.argv = ['pwman3', '-d', '', '-c', 'dummy.conf']
  377. p2 = parser_options()
  378. args = p2.parse_args()
  379. self.assertRaises(Exception, get_conf_options, args, False)
  380. config._conf = _save_conf.copy()
  381. os.unlink('dummy.conf')
  382. def test_set_xsel(self):
  383. set_xsel(config, False)
  384. set_xsel(config, True)
  385. if sys.platform == 'linux2':
  386. self.assertEqual(None, config._conf['Global']['xsel'])
  387. def test_get_conf_file(self):
  388. Args = namedtuple('args', 'cfile')
  389. args = Args(cfile='nosuchfile')
  390. # setting the default
  391. # in case the user specifies cfile as command line option
  392. # and that file does not exist!
  393. foo = config._conf.copy()
  394. get_conf_file(args)
  395. # args.cfile does not exist, hence the config values
  396. # should be the same as in the defaults
  397. config.set_config(foo)
  398. def test_get_conf_options(self):
  399. Args = namedtuple('args', 'cfile, dbase, algo')
  400. args = Args(cfile='nosuchfile', dbase='dummy.db', algo='AES')
  401. self.assertRaises(Exception, get_conf_options, (args, 'False'))
  402. config._defaults['Database']['type'] = 'SQLite'
  403. # config._conf['Database']['type'] = 'SQLite'
  404. xsel, dbtype = get_conf_options(args, 'True')
  405. self.assertEqual(dbtype, 'SQLite')
  406. def test_set_conf(self):
  407. set_conf_f = getattr(config, 'set_conf')
  408. private_conf = getattr(config, '_conf')
  409. set_conf_f({'Config': 'OK'})
  410. self.assertDictEqual({'Config': 'OK'}, config._conf)
  411. config._conf = private_conf
  412. def test_umask(self):
  413. config._defaults = {'Global': {}}
  414. self.assertRaises(config.ConfigException, set_umask, config)
  415. def tearDown(self):
  416. config._conf = self.orig_config.copy()