#!/usr/bin/env python # # Revelation 0.4.0 - a password manager for GNOME 2 # http://oss.codepoet.no/revelation/ # $Id$ # # Unit tests for Revelation DataHandler module # # # Copyright (c) 2003-2005 Erik Grinaker # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # import unittest, re, xml.dom.minidom from revelation import data, datahandler, entry class RevelationXML(unittest.TestCase): "RevelationXML" def test_attrs(self): "RevelationXML has sane attributes" self.assertNotEqual(datahandler.RevelationXML.name, "") self.assertEqual(datahandler.RevelationXML.importer, True) self.assertEqual(datahandler.RevelationXML.exporter, True) self.assertEqual(datahandler.RevelationXML.encryption, False) class RevelationXML_check(unittest.TestCase): "RevelationXML.check()" def test_inv_dataversion(self): "RevelationXML.check() raises VersionError on invalid version" handler = datahandler.RevelationXML() xml = """ """ self.assertRaises(datahandler.VersionError, handler.check, xml) def test_inv_docelement(self): "RevelationXML.check() rejects invalid document element" handler = datahandler.RevelationXML() xml = """ """ self.assertRaises(datahandler.FormatError, handler.check, xml) def test_inv_xmlheader(self): "RevelationXML.check() rejects invalid header" handler = datahandler.RevelationXML() xml = """ """ self.assertRaises(datahandler.FormatError, handler.check, xml) def test_none(self): "RevelationXML.check() handles None" handler = datahandler.RevelationXML() self.assertRaises(datahandler.FormatError, handler.check, None) def test_valid(self): "RevelationXML.check() accepts valid input" handler = datahandler.RevelationXML() xml = """ """ self.assertEqual(handler.check(xml), None) class RevelationXML_detect(unittest.TestCase): "RevelationXML.detect()" def test_inv(self): "RevelationXML.detect() rejects invalid input" handler = datahandler.RevelationXML() self.assertEqual(handler.detect("dummydata"), False) def test_inv_version(self): "RevelationXML.detect() rejects invalid version" handler = datahandler.RevelationXML() xml = """ """ self.assertEquals(handler.detect(xml), False) def test_none(self): "RevelationXML.detect() handles None" handler = datahandler.RevelationXML() self.assertEquals(handler.detect(None), False) def test_valid(self): "RevelationXML.detect() detects valid input" handler = datahandler.RevelationXML() xml = """ """ self.assertEqual(handler.detect(xml), True) class RevelationXML_export_data(unittest.TestCase): "RevelationXML.export_data()" def test_entrydata(self): "RevelationXML.export_data() exports all entry data" handler = datahandler.RevelationXML() entrystore = data.EntryStore() e = entry.GenericEntry() e.name = "Generic entry" e.description = "A test entry" e.updated = 1098822251 e.get_field(entry.HostnameField).value = "localhost" e.get_field(entry.UsernameField).value = "erikg" e.get_field(entry.PasswordField).value = "pwtest" entrystore.add_entry(e) entrystore2 = handler.import_data(handler.export_data(entrystore)) e2 = entrystore2.get_entry(entrystore2.iter_nth_child(None, 0)) self.assertEquals(e.name, e2.name) self.assertEquals(e.description, e2.description) self.assertEquals(e.updated, e2.updated) self.assertEquals(e.get_field(entry.HostnameField).value, e2.get_field(entry.HostnameField).value) self.assertEquals(e.get_field(entry.UsernameField).value, e2.get_field(entry.UsernameField).value) self.assertEquals(e.get_field(entry.PasswordField).value, e2.get_field(entry.PasswordField).value) def test_validxml(self): "RevelationXML.export_data() generates well-formed XML" handler = datahandler.RevelationXML() entrystore = data.EntryStore() e = entry.FolderEntry() e.name = "Folder" folderiter = entrystore.add_entry(e) e = entry.GenericEntry() e.name = "Generic entry" e.description = "A test entry" e.updated = 1098822251 e.get_field(entry.HostnameField).value = "localhost" e.get_field(entry.UsernameField).value = "erikg" e.get_field(entry.PasswordField).value = "pwtest" entrystore.add_entry(e, folderiter) entrystore.add_entry(e) xmldata = handler.export_data(entrystore) xml.dom.minidom.parseString(xmldata) class RevelationXML_import_data(unittest.TestCase): "RevelationXML.import_data()" def test_emptyentry(self): "RevelationXML.import_data() handles empty entries" handler = datahandler.RevelationXML() xml = """ """ entrystore = handler.import_data(xml) e = entrystore.get_entry(entrystore.iter_nth_child(None, 0)) self.assertNotEqual(e, None) self.assertEqual(e.name, "") self.assertEqual(e.description, "") self.assertNotEqual(e.updated, 0) self.assertEqual(type(e), entry.GenericEntry) self.assertEqual(e.get_field(entry.HostnameField).value, "") self.assertEqual(e.get_field(entry.UsernameField).value, "") self.assertEqual(e.get_field(entry.PasswordField).value, "") def test_entrydata(self): "RevelationXML.import_data() loads all entry data" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry 1098738771 localhost erikg pwtest """ entrystore = handler.import_data(xml) e = entrystore.get_entry(entrystore.iter_nth_child(None, 0)) self.assertEqual(e.name, "Generic entry") self.assertEqual(e.description, "A test entry") self.assertEqual(e.updated, 1098738771) self.assertEqual(type(e), entry.GenericEntry) self.assertEqual(e.get_field(entry.HostnameField).value, "localhost") self.assertEqual(e.get_field(entry.UsernameField).value, "erikg") self.assertEqual(e.get_field(entry.PasswordField).value, "pwtest") def test_entrydata(self): "RevelationXML.import_data() handles all entry types" handler = datahandler.RevelationXML() typedata = { "folder" : [], "creditcard" : [ "creditcard-cardtype", "creditcard-cardnumber", "creditcard-expirydate", "creditcard-ccv", "generic-pin" ], "cryptokey" : [ "generic-hostname", "generic-certificate", "generic-keyfile", "generic-password" ], "database" : [ "generic-hostname", "generic-username", "generic-password", "generic-database" ], "door" : [ "generic-location", "generic-code" ], "email" : [ "generic-email", "generic-hostname", "generic-username", "generic-password" ], "ftp" : [ "generic-hostname", "generic-port", "generic-username", "generic-password" ], "generic" : [ "generic-hostname", "generic-username", "generic-password" ], "phone" : [ "phone-phonenumber", "generic-pin" ], "shell" : [ "generic-hostname", "generic-domain", "generic-username", "generic-password" ], "website" : [ "generic-url", "generic-username", "generic-password" ] } xml = """""" for typename, fieldlist in typedata.items(): xml += "" for field in fieldlist: xml += "" xml += "" xml += "" handler.import_data(xml) def test_inv_entryfield(self): "RevelationXML.import_data() raises DataError on invalid entry field" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry 1098738771 invalid field type erikg pwtest """ self.assertRaises(datahandler.DataError, handler.import_data, xml) def test_inv_entrytype(self): "RevelationXML.import_data() raises DataError on invalid entry type" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry 1098738771 localhost erikg pwtest """ self.assertRaises(datahandler.DataError, handler.import_data, xml) def test_inv_fieldtype(self): "RevelationXML.import_data() raises DataError on invalid field type" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry 1098738771 localhost erikg pwtest """ self.assertRaises(datahandler.DataError, handler.import_data, xml) def test_inv_node(self): "RevelationXML.import_data() raises FormatError on unknown nodes" handler = datahandler.RevelationXML() xml = """ """ xml2 = """ """ self.assertRaises(datahandler.FormatError, handler.import_data, xml) self.assertRaises(datahandler.FormatError, handler.import_data, xml2) def test_missing_entrytype(self): "RevelationXML.import_data() raises FormatError on missing entrytype" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry 1098738771 localhost erikg pwtest """ self.assertRaises(datahandler.FormatError, handler.import_data, xml) def test_missing_fieldid(self): "RevelationXML.import_data() raises FormatError on missing fieldid" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry 1098738771 localhost erikg pwtest """ self.assertRaises(datahandler.FormatError, handler.import_data, xml) def test_parentfolder(self): "RevelationXML.import_data() raises DataError when non-folder has children" handler = datahandler.RevelationXML() xml = """ Non-folder An invalid parent 1098738862 Generic entry A test entry 1098738771 localhost erikg pwtest """ self.assertRaises(datahandler.DataError, handler.import_data, xml) def test_inv_updated(self): "RevelationXML.import_data() raises DataError on invalid updatetime" handler = datahandler.RevelationXML() xml = """ Generic entry A test entry dummy123 localhost erikg pwtest """ self.assertRaises(datahandler.DataError, handler.import_data, xml) def test_valid(self): "RevelationXML.import_data() accepts valid input" handler = datahandler.RevelationXML() xml = """ Folder A test folder 1098738862 Generic entry A test entry 1098738771 localhost erikg pwtest """ self.assertEqual(type(handler.import_data(xml)), data.EntryStore) class Revelation(unittest.TestCase): "Revelation" def test_attrs(self): "Revelation has sane attributes" self.assertNotEqual(datahandler.Revelation.name, "") self.assertEqual(datahandler.Revelation.importer, True) self.assertEqual(datahandler.Revelation.exporter, True) self.assertEqual(datahandler.Revelation.encryption, True) class Revelation_check(unittest.TestCase): "Revelation.check()" def test_invalid(self): "Revelation.check() raises FormatError on invalid data" handler = datahandler.Revelation() data = "xxx\x00\x01\x00\x00\x04\x00\x00\x00\x00" + ("\0" * 32) self.assertRaises(datahandler.FormatError, handler.check, data) data = "rvl\x00\x01\x00\x00\x00\x00jej" + ("\0" * 32) self.assertRaises(datahandler.FormatError, handler.check, data) def test_none(self): "Revelation.check() raises FormatError on None" handler = datahandler.Revelation() self.assertRaises(datahandler.FormatError, handler.check, None) def test_short(self): "Revelation.check() raises FormatError on short data" handler = datahandler.Revelation() data = "rvl\x00\x01\x00\x00\x04\x00\x00\x00\x00" self.assertRaises(datahandler.FormatError, handler.check, data) def test_valid(self): "Revelation.check() handles valid data" handler = datahandler.Revelation() data = "rvl\x00\x01\x00\x00\x04\x00\x00\x00\x00" + ("\0" * 32) handler.check(data) def test_version(self): "Revelation.check() raises VersionError on unknown version" handler = datahandler.Revelation() data = "rvl\x00\x02\x00\x00\x04\x00\x00\x00\x00" + ("\0" * 32) self.assertRaises(datahandler.VersionError, handler.check, data) class Revelation_detect(unittest.TestCase): "Revelation.detect()" def test_invalid(self): "Revelation.detect() rejects invalid data" handler = datahandler.Revelation() data = "xxx\x00\x01\x00\x00\x04\x00\x00\x00\x00" + ("\0" * 32) self.assertEquals(handler.detect(data), False) data = "rvl\x00\x01\x00\x00\x00\x00jej" + ("\0" * 32) self.assertEquals(handler.detect(data), False) def test_none(self): "Revelation.detect() rejects None" handler = datahandler.Revelation() self.assertEquals(handler.detect(None), False) def test_short(self): "Revelation.detect() rejects short data" handler = datahandler.Revelation() data = "rvl\x00\x01\x00\x00\x04\x00\x00\x00\x00" self.assertEquals(handler.detect(data), False) def test_valid(self): "Revelation.detect() handles valid data" handler = datahandler.Revelation() data = "rvl\x00\x01\x00\x00\x04\x00\x00\x00\x00" + ("\0" * 32) self.assertEquals(handler.detect(data), True) def test_version(self): "Revelation.detect() rejects unknown versions" handler = datahandler.Revelation() data = "rvl\x00\x02\x00\x00\x04\x00\x00\x00\x00" + ("\0" * 32) self.assertEquals(handler.detect(data), False) class Revelation_export_data(unittest.TestCase): "Revelation.export_data()" def setUp(self): # set up a common password self.password = "test123" # set up an entrystore for tests self.entrystore = data.EntryStore() e = entry.FolderEntry() e.name = "Testfolder" e.description = "Just a test folder" folderiter = self.entrystore.add_entry(e) e = entry.GenericEntry() e.name = "Generic child" e.description = "Child-entry" self.entrystore.add_entry(e, folderiter) e = entry.GenericEntry() e.name = "Another test-entry" self.entrystore.add_entry(e) def test_header(self): "Revelation.export_data() generates a valid header" handler = datahandler.Revelation() data = handler.export_data(self.entrystore, self.password) match = re.match(""" ^ # start of line rvl\x00 # magic string (.) # data version \x00 # separator (.{3}) # app version \x00\x00\x00 # separator """, data, re.VERBOSE) self.assertNotEqual(match, None) self.assertEqual(ord(match.group(1)), 1) self.assertEqual(ord(match.group(2)[0]) < 10, True) self.assertEqual(ord(match.group(2)[1]) < 10, True) self.assertEqual(ord(match.group(2)[2]) < 10, True) def test_length(self): "Revelation.export_data() generates data of correct length" handler = datahandler.Revelation() data = handler.export_data(self.entrystore, self.password) self.assertEqual(len(data) >= 28, True) self.assertEqual((len(data) - 12) % 16, 0) def test_password_none(self): "Revelation.export_data() raises PasswordError on None password" handler = datahandler.Revelation() self.assertRaises(datahandler.PasswordError, handler.export_data, self.entrystore, None) def test_random(self): "Revelation.export_data() gives different results each run" handler = datahandler.Revelation() self.assertNotEqual( handler.export_data(self.entrystore, self.password), handler.export_data(self.entrystore, self.password) ) def test_valid(self): "Revelation.export_data() generates valid data" handler = datahandler.Revelation() encdata = handler.export_data(self.entrystore, self.password) entrystore = handler.import_data(encdata, self.password) self.assertEqual(type(entrystore), type(self.entrystore)) e1 = self.entrystore.get_entry(self.entrystore.iter_nth_child(None, 1)) e2 = entrystore.get_entry(entrystore.iter_nth_child(None, 1)) self.assertEqual(type(e1), type(e2)) self.assertEqual(e1.name, e2.name) self.assertEqual(e1.description, e2.description) self.assertEqual(e1.updated, e2.updated) for f1, f2 in zip(e1.fields, e2.fields): self.assertEqual(f1.value, f2.value) class Revelation_import_data(unittest.TestCase): "Revelation.import_data()" def setUp(self): # set up a common password self.password = "test123" # set up an entrystore for tests self.entrystore = data.EntryStore() e = entry.FolderEntry() e.name = "Testfolder" e.description = "Just a test folder" folderiter = self.entrystore.add_entry(e) e = entry.GenericEntry() e.name = "Generic child" e.description = "Child-entry" self.entrystore.add_entry(e, folderiter) e = entry.GenericEntry() e.name = "Another test-entry" self.entrystore.add_entry(e) handler = datahandler.Revelation() self.data = handler.export_data(self.entrystore, self.password) def test_inv_header(self): "Revelation.import_data() raises FormatError on invalid header" handler = datahandler.Revelation() self.assertRaises(datahandler.FormatError, handler.import_data, "123" + self.data[3:], self.password) def test_inv_length(self): "Revelation.import_data() raises FormatError on invalid length" handler = datahandler.Revelation() self.assertRaises(datahandler.FormatError, handler.import_data, self.data + "123", self.password) def test_inv_password(self): "Revelation.import_data() raises PasswordError on invalid password" handler = datahandler.Revelation() self.assertRaises(datahandler.PasswordError, handler.import_data, self.data, "dummypassword") def test_inv_version(self): "Revelation.import_data() raises VersionError on invalid version" handler = datahandler.Revelation() self.assertRaises(datahandler.VersionError, handler.import_data, "rvl\x00\x00" + self.data[5:], self.password) self.assertRaises(datahandler.VersionError, handler.import_data, "rvl\x00\x02" + self.data[5:], self.password) def test_password_long(self): "Revelation.import_data() handles long (>32 chars) passwords" handler = datahandler.Revelation() data = handler.export_data(self.entrystore, "abcdefgh12345678abcdefgh12345678abcdefgh") entrystore = handler.import_data(data, "abcdefgh12345678abcdefgh12345678abcdefgh") def test_password_none(self): "Revelation.import_data() raises PasswordError on None password" handler = datahandler.Revelation() self.assertRaises(datahandler.PasswordError, handler.import_data, self.data, None) def test_valid(self): "Revelation.import_data() accepts valid data" handler = datahandler.Revelation() entrystore = handler.import_data(self.data, self.password) self.assertEqual(type(entrystore), type(self.entrystore)) e1 = self.entrystore.get_entry(self.entrystore.iter_nth_child(None, 1)) e2 = entrystore.get_entry(entrystore.iter_nth_child(None, 1)) self.assertEqual(type(e1), type(e2)) self.assertEqual(e1.name, e2.name) self.assertEqual(e1.description, e2.description) self.assertEqual(e1.updated, e2.updated) for f1, f2 in zip(e1.fields, e2.fields): self.assertEqual(f1.value, f2.value) if __name__ == "__main__": unittest.main()