class EnvironmentTestCase(unittest.TestCase): def setUp(self): env_path = os.path.join(tempfile.gettempdir(), 'trac-tempenv') self.env = Environment(env_path, create=True, db_str='sqlite:db/trac.db') self.db = self.env.get_db_cnx() def tearDown(self): self.db.close() self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_get_version(self): """Testing env.get_version""" assert self.env.get_version() == db_default.db_version def test_get_known_users(self): """Testing env.get_known_users""" cursor = self.db.cursor() cursor.execute("INSERT INTO session " "VALUES ('123',0,'email','*****@*****.**')") cursor.executemany("INSERT INTO session VALUES (%s,1,%s,%s)", [('tom', 'name', 'Tom'), ('tom', 'email', '*****@*****.**'), ('joe', 'email', '*****@*****.**'), ('jane', 'name', 'Jane')]) users = {} for username, name, email in self.env.get_known_users(self.db): users[username] = (name, email) assert not users.has_key('anonymous') self.assertEqual(('Tom', '*****@*****.**'), users['tom']) self.assertEqual((None, '*****@*****.**'), users['joe']) self.assertEqual(('Jane', None), users['jane'])
def rename_user(envpath, oldname, newname): """Deletes all watchlist DB entries => Uninstaller""" from trac.env import Environment try: env = Environment(envpath) except: print "Given path '%s' seems not to be a Trac environment." % envpath sys.exit(3) db = env.get_db_cnx() cursor = db.cursor() try: cursor.execute(""" UPDATE watchlist SET wluser=%s WHERE wluser=%s """, (newname,oldname)) cursor.execute(""" UPDATE watchlist_settings SET wluser=%s WHERE wluser=%s """, (newname,oldname)) print "Renamed user '%s' to '%s'." % (oldname,newname) db.commit() except Exception as e: db.rollback() print "Could not rename user: "******"Does the new user already exists?" sys.exit(3) db.commit() print "Finished."
class TracAdminDeployTestCase(TracAdminTestCaseBase): """Tests for the trac-admin deploy command.""" def setUp(self): self.env = Environment(path=mkdtemp(), create=True) self.admin = TracAdmin(self.env.path) self.admin.env_set('', self.env) def tearDown(self): self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_deploy(self): """Deploy into valid target directory.""" target = os.path.join(self.env.path, 'www') htdocs_dir = os.path.join(target, 'htdocs') rv, output = self.execute('deploy %s' % target) self.assertEqual(0, rv, output) self.assertExpectedResult(output) self.assertTrue(os.path.exists(os.path.join(target, 'cgi-bin'))) self.assertTrue(os.path.exists(htdocs_dir)) self.assertTrue(os.path.exists(os.path.join(htdocs_dir, 'common'))) self.assertTrue(os.path.exists(os.path.join(htdocs_dir, 'site'))) def test_deploy_to_invalid_target_raises_error(self): """Running deploy with target directory equal to or below the source directory raises AdminCommandError. """ rv, output = self.execute('deploy %s' % self.env.htdocs_dir) self.assertEqual(2, rv, output) self.assertExpectedResult(output)
class EnvironmentTestCase(unittest.TestCase): def setUp(self): env_path = os.path.join(tempfile.gettempdir(), 'trac-tempenv') self.env = Environment(env_path, create=True) self.db = self.env.get_db_cnx() def tearDown(self): self.db.close() self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_get_version(self): """Testing env.get_version""" assert self.env.get_version() == db_default.db_version def test_get_known_users(self): """Testing env.get_known_users""" cursor = self.db.cursor() cursor.executemany("INSERT INTO session VALUES (%s,%s,0)", [('123', 0),('tom', 1), ('joe', 1), ('jane', 1)]) cursor.executemany("INSERT INTO session_attribute VALUES (%s,%s,%s,%s)", [('123', 0, 'email', '*****@*****.**'), ('tom', 1, 'name', 'Tom'), ('tom', 1, 'email', '*****@*****.**'), ('joe', 1, 'email', '*****@*****.**'), ('jane', 1, 'name', 'Jane')]) users = {} for username,name,email in self.env.get_known_users(self.db): users[username] = (name, email) assert not users.has_key('anonymous') self.assertEqual(('Tom', '*****@*****.**'), users['tom']) self.assertEqual((None, '*****@*****.**'), users['joe']) self.assertEqual(('Jane', None), users['jane'])
def __init__(self, path): self.env = Environment(path) self.loginNameCache = {} self.fieldNameCache = {} from trac.db.api import DatabaseManager self.using_postgres = \ DatabaseManager(self.env).connection_uri.startswith("postgres:")
class TracAdminDeployTestCase(TracAdminTestCaseBase): """Tests for the trac-admin deploy command.""" stdout = None stderr = None devnull = None @classmethod def setUpClass(cls): cls.stdout = sys.stdout cls.stderr = sys.stderr cls.devnull = io.open(os.devnull, 'wb') sys.stdout = sys.stderr = cls.devnull @classmethod def tearDownClass(cls): cls.devnull.close() sys.stdout = cls.stdout sys.stderr = cls.stderr def setUp(self): self.env = Environment(path=mkdtemp(), create=True) self.admin = TracAdmin(self.env.path) self.admin.env_set('', self.env) def tearDown(self): self.env.shutdown() rmtree(self.env.path) def test_deploy(self): target = os.path.join(self.env.path, 'www') shebang = ('#!' + sys.executable).encode('utf-8') rv, output = self.execute('deploy %s' % target) self.assertEqual(0, rv, output) self.assertExpectedResult(output) self.assertTrue(os.path.exists(os.path.join(target, 'cgi-bin'))) self.assertTrue(os.path.exists(os.path.join(target, 'htdocs'))) self.assertTrue(os.path.exists(os.path.join(target, 'htdocs', 'common'))) self.assertTrue(os.path.exists(os.path.join(target, 'htdocs', 'site'))) self.assertTrue(os.path.isfile(os.path.join( target, 'htdocs', 'common', 'js', 'trac.js'))) self.assertTrue(os.path.isfile(os.path.join( target, 'htdocs', 'common', 'css', 'trac.css'))) for ext in ('cgi', 'fcgi', 'wsgi'): content = read_file(os.path.join(target, 'cgi-bin', 'trac.%s' % ext), 'rb') self.assertIn(shebang, content) self.assertEqual(0, content.index(shebang)) self.assertIn(repr(self.env.path).encode('ascii'), content) def test_deploy_to_invalid_target_raises_error(self): """Running deploy with target directory equal to or below the source directory raises AdminCommandError. """ rv, output = self.execute('deploy %s' % self.env.htdocs_dir) self.assertEqual(2, rv, output) self.assertExpectedResult(output)
def setUp(self): env_path = tempfile.mkdtemp(prefix='trac-tempenv-') # self.addCleanup(self.cleanupEnvPath, env_path) self.env = Environment(env_path, create=True) self.env.config.set('trac', 'base_url', 'http://trac.edgewall.org/some/path') self.env.config.save()
def rename_user(envpath, oldname, newname): """Deletes all watchlist DB entries => Uninstaller""" from trac.env import Environment try: env = Environment(envpath) except: print "Given path '%s' seems not to be a Trac environment." % envpath sys.exit(3) db = env.get_db_cnx() cursor = db.cursor() try: cursor.execute( """ UPDATE watchlist SET wluser=%s WHERE wluser=%s """, (newname, oldname)) cursor.execute( """ UPDATE watchlist_settings SET wluser=%s WHERE wluser=%s """, (newname, oldname)) print "Renamed user '%s' to '%s'." % (oldname, newname) db.commit() except Exception as e: db.rollback() print "Could not rename user: "******"Does the new user already exists?" sys.exit(3) db.commit() print "Finished."
class EnvironmentTestCase(unittest.TestCase): def setUp(self): env_path = os.path.join(tempfile.gettempdir(), 'trac-tempenv') self.env = Environment(env_path, create=True) def tearDown(self): with self.env.db_query as db: db.close() self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_get_version(self): """Testing env.get_version""" assert self.env.get_version() == db_default.db_version def test_get_known_users(self): """Testing env.get_known_users""" with self.env.db_transaction as db: db.executemany("INSERT INTO session VALUES (%s,%s,0)", [('123', 0), ('tom', 1), ('joe', 1), ('jane', 1)]) db.executemany( "INSERT INTO session_attribute VALUES (%s,%s,%s,%s)", [('123', 0, 'email', '*****@*****.**'), ('tom', 1, 'name', 'Tom'), ('tom', 1, 'email', '*****@*****.**'), ('joe', 1, 'email', '*****@*****.**'), ('jane', 1, 'name', 'Jane')]) users = {} for username, name, email in self.env.get_known_users(): users[username] = (name, email) assert not users.has_key('anonymous') self.assertEqual(('Tom', '*****@*****.**'), users['tom']) self.assertEqual((None, '*****@*****.**'), users['joe']) self.assertEqual(('Jane', None), users['jane'])
def __init__(self, path, append): self.append = _append self.env = Environment(path) self._db = self.env.get_db_cnx() self._db.autocommit = False self.loginNameCache = {} self.fieldNameCache = {}
def _test_convert_with_plugin_to_sqlite_env(self): self.src_env = Environment(self.src_path) self.assertTrue(self.src_env.needs_upgrade()) self.src_env.upgrade() self.assertFalse(self.src_env.needs_upgrade()) src_options = self._get_options(self.src_env) src_records = self._get_all_records(self.src_env) self._convert_db(self.src_env, 'sqlite:db/trac.db', self.dst_path) self.dst_env = Environment(self.dst_path) self.assertFalse(self.dst_env.needs_upgrade()) self.assertFalse( os.path.exists(os.path.join(self.dst_env.log_dir, 'created'))) self.assertTrue( os.path.exists(os.path.join(self.dst_env.log_dir, 'upgraded'))) dst_options = self._get_options(self.dst_env) dst_records = self._get_all_records(self.dst_env) self.assertEqual({ 'name': 'initial_database_version', 'value': '21' }, dst_records['system']['initial_database_version']) self._compare_records(src_records, dst_records) self.assertEqual(src_options, dst_options) att = Attachment(self.dst_env, 'wiki', 'WikiStart', 'filename.txt') self.assertEqual('test', read_file(att.path))
def fetchRecipes(trac_env): env = Environment(trac_env) db = env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT path,active,recipe,min_rev,max_rev,label,description,name FROM bitten_config") for row in cursor: (path, active, recipe, min_rev, max_rev, label, description, name) = row writeFile(trac_env, name, (path, active, recipe, min_rev, max_rev, label, description))
def _do_migrate(self, env_path, dburl): options = [('trac', 'database', dburl)] options.extend((section, name, value) for section in self.config.sections() for name, value in self.config.options(section) if section != 'trac' or name != 'database') src_db = self.env.get_read_db() src_cursor = src_db.cursor() src_tables = set(self._get_tables(self.config.get('trac', 'database'), src_cursor)) env = Environment(env_path, create=True, options=options) env.upgrade() env.config.save() # remove comments db = env.get_read_db() cursor = db.cursor() tables = set(self._get_tables(dburl, cursor)) tables = sorted(tables & src_tables) sequences = set(self._get_sequences(dburl, cursor, tables)) directories = self._get_directories(src_db) printout('Copying tables:') for table in tables: if table == 'system': continue @env.with_transaction() def copy(db): cursor = db.cursor() printout(' %s table... ' % table, newline=False) src_cursor.execute('SELECT * FROM ' + src_db.quote(table)) columns = get_column_names(src_cursor) query = 'INSERT INTO ' + db.quote(table) + \ ' (' + ','.join(db.quote(c) for c in columns) + ')' + \ ' VALUES (' + ','.join(['%s'] * len(columns)) + ')' cursor.execute('DELETE FROM ' + db.quote(table)) count = 0 while True: rows = src_cursor.fetchmany(100) if not rows: break cursor.executemany(query, rows) count += len(rows) printout('%d records.' % count) if table in sequences: db.update_sequence(cursor, table) printout('Copying directories:') for name in directories: printout(' %s directory... ' % name, newline=False) src = os.path.join(self.env.path, name) dst = os.path.join(env.path, name) if os.path.isdir(dst): shutil.rmtree(dst) if os.path.isdir(src): shutil.copytree(src, dst) printout('done.')
def _do_migrate(self, env_path, dburl): options = [('trac', 'database', dburl)] options.extend((section, name, value) for section in self.config.sections() for name, value in self.config.options(section) if section != 'trac' or name != 'database') src_db = self.env.get_read_db() src_cursor = src_db.cursor() src_tables = set( self._get_tables(self.config.get('trac', 'database'), src_cursor)) env = Environment(env_path, create=True, options=options) env.upgrade() env.config.save() # remove comments db = env.get_read_db() cursor = db.cursor() tables = set(self._get_tables(dburl, cursor)) tables = sorted(tables & src_tables) sequences = set(self._get_sequences(dburl, cursor, tables)) directories = self._get_directories(src_db) printout('Copying tables:') for table in tables: if table == 'system': continue @env.with_transaction() def copy(db): cursor = db.cursor() printout(' %s table... ' % table, newline=False) src_cursor.execute('SELECT * FROM ' + src_db.quote(table)) columns = get_column_names(src_cursor) query = 'INSERT INTO ' + db.quote(table) + \ ' (' + ','.join(db.quote(c) for c in columns) + ')' + \ ' VALUES (' + ','.join(['%s'] * len(columns)) + ')' cursor.execute('DELETE FROM ' + db.quote(table)) count = 0 while True: rows = src_cursor.fetchmany(100) if not rows: break cursor.executemany(query, rows) count += len(rows) printout('%d records.' % count) if table in sequences: db.update_sequence(cursor, table) printout('Copying directories:') for name in directories: printout(' %s directory... ' % name, newline=False) src = os.path.join(self.env.path, name) dst = os.path.join(env.path, name) if os.path.isdir(dst): shutil.rmtree(dst) if os.path.isdir(src): shutil.copytree(src, dst) printout('done.')
def __init__(self, path): self.env = Environment(path) self._db = self.env.get_db_cnx() self._db.autocommit = False self.loginNameCache = {} self.fieldNameCache = {} from trac.db.api import DatabaseManager self.using_postgres = DatabaseManager( self.env).connection_uri.startswith("postgres:")
def Main(opts): """ Cross your fingers and pray """ env = Environment(opts.envpath) from tractags.api import TagSystem tlist = opts.tags or split_tags(env.config.get('blog', 'default_tag', 'blog')) tags = TagSystem(env) req = Mock(perm=MockPerm()) blog = tags.query(req, ' '.join(tlist + ['realm:wiki'])) cnx = env.get_db_cnx() for resource, page_tags in list(blog): try: page = WikiPage(env, version=1, name=resource.id) _, publish_time, author, _, _ = page.get_history().next() if opts.deleteonly: page.delete() continue categories = ' '.join([t for t in page_tags if t not in tlist]) page = WikiPage(env, name=resource.id) for version, version_time, version_author, version_comment, \ _ in page.get_history(): # Currently the basename of the post url is used due to # http://trac-hacks.org/ticket/2956 #name = resource.id.replace('/', '_') name = resource.id # extract title from text: fulltext = page.text match = _title_split_match(fulltext) if match: title = match.group(1) fulltext = match.group(2) else: title = name body = fulltext print "Adding post %s, v%s: %s" % (name, version, title) insert_blog_post(cnx, name, version, title, body, publish_time, version_time, version_comment, version_author, author, categories) reparent_blog_attachments(env, resource.id, name) continue cnx.commit() if opts.delete: page.delete() continue except: env.log.debug("Error loading wiki page %s" % resource.id, exc_info=True) print "Failed to add post %s, v%s: %s" % (name, version, title) cnx.rollback() cnx.close() return 1 cnx.close() return 0
def _create_env(self, path, dburi): env = Environment(path, True, [('trac', 'database', dburi), ('trac', 'base_url', 'http://localhost/'), ('project', 'name', u'Pŕójéćŧ Ńáḿé')]) dbm = DatabaseManager(env) dbm.set_database_version(21, 'initial_database_version') att = Attachment(env, 'wiki', 'WikiStart') att.insert('filename.txt', io.BytesIO('test'), 4) env.shutdown()
def __init__(self, path): '''Basic object that supports the wiki and all that jazz''' self._env = Environment(os.path.abspath(path)) purl = self._env.project_url if purl.endswith('/'): purl = purl[:-1] if purl: self.name = purl.split('/')[-1] else: self.name = self._env.project_name
def get_trac_user(path, username): from trac.env import Environment env = Environment(path) db = env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT name, value" " FROM session_attribute" " WHERE sid='%s'" " AND (name='email' OR name='name')" % username ) return dict((name, value) for name, value in cursor)
def globally_execute_command(self, *args): offset = 0 max_index = -1 if args and args[0].isdigit(): offset = int(args[0]) if len(args) > 1 and args[1].isdigit(): limit = int(args[1]) max_index = limit + offset args = args[2:] else: args = args[1:] upgrade_check = False env_list = False if args and args[0] == 'upgrade-check': upgrade_check = True elif args and args[0] == 'list-env': env_list = True sys_home_project_name = self.config.get('multiproject', 'sys_home_project_name') for index, row in enumerate(self.projects_iterator(['env_name'], batch_size=10)): env_name, = row if index < offset: continue if max_index != -1 and index >= max_index: break if env_name == sys_home_project_name: continue if env_list: printout("{0:4} env:'{1}'".format(index, env_name)) continue env = None try: env_path = safe_path(self.config.get('multiproject', 'sys_projects_root'), env_name) env = Environment(env_path) except TracError as e: printout(_('ERROR: Opening environment %(env_name)s failed', env_name=env_name)) continue if upgrade_check: if env.needs_upgrade(): printout("[+] {0:4} env:'{1}'".format(index, env_name)) else: printout("[ ] {0:4} env:'{1}'".format(index, env_name)) continue # To setup MultiProject specific things like 'project_identifier' MultiProjectEnvironmentInit(env).environment_needs_upgrade(None) try: command_manager = AdminCommandManager(env) printout(_("{0:4} Run in env:'{1}'".format(index, env_name))) command_manager.execute_command(*args) except AdminCommandError as e: printout(_('ERROR: Executing command in environment %(env_name)s failed: ', env_name=env_name) + str(e))
def Main(opts): """ Cross your fingers and pray """ env = Environment(opts.envpath) from tractags.api import TagSystem tlist = opts.tags or split_tags( env.config.get('blog', 'default_tag', 'blog')) tags = TagSystem(env) req = Mock(perm=MockPerm()) blog = tags.query(req, ' '.join(tlist + ['realm:wiki'])) cnx = env.get_db_cnx() for resource, page_tags in list(blog): try: page = WikiPage(env, version=1, name=resource.id) _, publish_time, author, _, _ = page.get_history().next() if opts.deleteonly: page.delete() continue categories = ' '.join([t for t in page_tags if t not in tlist]) page = WikiPage(env, name=resource.id) for version, version_time, version_author, version_comment, \ _ in page.get_history(): # Currently the basename of the post url is used due to # http://trac-hacks.org/ticket/2956 #name = resource.id.replace('/', '_') name = resource.id # extract title from text: fulltext = page.text match = _title_split_match(fulltext) if match: title = match.group(1) fulltext = match.group(2) else: title = name body = fulltext print "Adding post %s, v%s: %s" % (name, version, title) insert_blog_post(cnx, name, version, title, body, publish_time, version_time, version_comment, version_author, author, categories) reparent_blog_attachments(env, resource.id, name) continue cnx.commit() if opts.delete: page.delete() continue except: env.log.debug("Error loading wiki page %s" % resource.id, exc_info=True) print "Failed to add post %s, v%s: %s" % (name, version, title) cnx.rollback() cnx.close() return 1 cnx.close() return 0
class TicketTemplateTestCase(unittest.TestCase): def setUp(self): # self.env = EnvironmentStub() env_path = os.path.join(tempfile.gettempdir(), 'trac-tempenv') self.env = Environment(env_path, create=True) self.db = self.env.get_db_cnx() self.compmgr = ComponentManager() # init TicketTemplateModule self.tt = ttadmin.TicketTemplateModule(self.compmgr) setattr(self.tt, "env", self.env) def tearDown(self): self.db.close() self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_get_active_navigation_item(self): req = Mock(path_info='/tickettemplate') self.assertEqual('tickettemplate', self.tt.get_active_navigation_item(req)) req = Mock(path_info='/something') self.assertNotEqual('tickettemplate', self.tt.match_request(req)) def test_get_navigation_items(self): req = Mock(href=Mock( tickettemplate=lambda: "/trac-tempenv/tickettemplate")) a, b, c = self.tt.get_navigation_items(req).next() self.assertEqual('mainnav', a) self.assertEqual('tickettemplate', b) def test_match_request(self): req = Mock(path_info='/tickettemplate') self.assertEqual(True, self.tt.match_request(req)) req = Mock(path_info='/something') self.assertEqual(False, self.tt.match_request(req)) def test_getTicketTypeNames(self): options = self.tt._getTicketTypeNames() self.assertEqual(["default", "defect", "enhancement", "task"], options) def test_loadSaveTemplateText(self): for tt_name, tt_text in [ ("default", "default text"), ("defect", "defect text"), ("enhancement", "enhancement text"), ("task", "task text"), ]: self.tt._saveTemplateText(tt_name, tt_text) self.assertEqual(tt_name + " text", self.tt._loadTemplateText(tt_name))
def __init__(self, config, dry_run=False): self._dry_run = dry_run self._milestones = config['milestones'] self._min_year = config['min_year'] self._env = Environment(config['env']) self._users = { key: value.strip() for key, _, value in self._env.get_known_users() if value }
def open_env(path): """Open another Environment.""" if isinstance(path, Environment): return path # Just in case elif '/' not in path: raise Exception, "Don't do this yet" head, tail = os.path.split(myenv.path) newpath = os.path.join(tail, path) return Environment(newpath) else: return Environment(path)
def do_purge(self, req, path, users): """Purge obsolete data - i.e. environment data (sessions, preferences, permissions) from users no longer existing @param req @param path path to the trac env to purge @param users users to keep @return boolean success @return msg info """ self.env.log.debug('+ Purging obsolete data') dryrun = self.env.config.getbool('user_sync','dryrun',True) sql = [] envpath, tracenv = os.path.split(path) try: env = Environment(path) except IOError: self.env.log.debug('Could not initialize environment at %s' % (path,)) return False, 'Could not initialize environment at %s' % (path,) perm = PermissionSystem(env) if not 'TRAC_ADMIN' in perm.get_user_permissions(req.perm.username): raise PermissionError excludes = self.get_perm_groups(path)+users protect = "'"+"','".join(excludes)+"'" self.env.log.debug("Excluding from purge: %s" % (protect,)) db = env.get_db_cnx() cursor = db.cursor() if not dryrun: self.env.log.debug('Updating database for %s' % (tracenv,)) cursor.execute('DELETE FROM auth_cookie WHERE name NOT IN (%s)' % (protect,)) cursor.execute('DELETE FROM session WHERE sid NOT IN (%s)' % (protect,)) cursor.execute('DELETE FROM session_attribute WHERE sid NOT IN (%s)' % (protect,)) cursor.execute('DELETE FROM permission WHERE username NOT IN (%s)' % (protect,)) db.commit() sql_file_path = self.env.config.get('user_sync','sql_file_path') or os.path.join(self.env.path,'log') if sql_file_path.lower() == 'none': self.env.log.debug('SQLFile disabled (sql_file_path is "none")') else: sqlfile = '%s.sql' % (tracenv,) sqlfile = os.path.join(sql_file_path,sqlfile) self.env.log.debug('Writing SQL to %s' % (sqlfile,)) try: f = open(sqlfile,'a') f.write('\n--- SQL for purging Trac environment %s\n' % (tracenv,)); f.write('DELETE FROM auth_cookie WHERE name NOT IN (%s);\n' % (protect,)) f.write('DELETE FROM session WHERE sid NOT IN (%s);\n' % (protect,)) f.write('DELETE FROM session_attribute WHERE sid NOT IN (%s);\n' % (protect,)) f.write('DELETE FROM permission WHERE username NOT IN (%s);\n' % (protect,)) except IOError: self.env.log.debug('Could not write SQL file %s!' % (sqlfile,)) return False, 'Could not write SQL file %s!' % (sqlfile,) return True, 'Successfully purged environment %s' % (tracenv,)
class TicketTemplateTestCase(unittest.TestCase): def setUp(self): # self.env = EnvironmentStub() env_path = os.path.join(tempfile.gettempdir(), 'trac-tempenv') self.env = Environment(env_path, create=True) self.db = self.env.db_transaction self.db.__enter__() self.compmgr = ComponentManager() # init TicketTemplateModule self.tt = ttadmin.TicketTemplateModule(self.compmgr) setattr(self.tt, "env", self.env) def tearDown(self): self.db.__exit__(None, None, None) self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_get_active_navigation_item(self): req = Mock(path_info='/tickettemplate') self.assertEqual('tickettemplate', self.tt.get_active_navigation_item(req)) req = Mock(path_info='/something') self.assertNotEqual('tickettemplate', self.tt.match_request(req)) def test_get_navigation_items(self): req = Mock(href=Mock(tickettemplate=lambda:"/trac-tempenv/tickettemplate")) a, b, c= self.tt.get_navigation_items(req).next() self.assertEqual('mainnav', a) self.assertEqual('tickettemplate', b) def test_match_request(self): req = Mock(path_info='/tickettemplate') self.assertEqual(True, self.tt.match_request(req)) req = Mock(path_info='/something') self.assertEqual(False, self.tt.match_request(req)) def test_getTicketTypeNames(self): options = self.tt._getTicketTypeNames() self.assertEqual(["default", "defect", "enhancement", "task"], options) def test_loadSaveTemplateText(self): for tt_name, tt_text in [("default", "default text"), ("defect", "defect text"), ("enhancement", "enhancement text"), ("task", "task text"), ]: self.tt._saveTemplateText(tt_name, tt_text) self.assertEqual(tt_name + " text", self.tt._loadTemplateText(tt_name))
def fetchRecipes(trac_env): env = Environment(trac_env) db = env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT path,active,recipe,min_rev,max_rev,label,description,name FROM bitten_config" ) for row in cursor: (path, active, recipe, min_rev, max_rev, label, description, name) = row writeFile(trac_env, name, (path, active, recipe, min_rev, max_rev, label, description))
def setUp(self): # self.env = EnvironmentStub() env_path = os.path.join(tempfile.gettempdir(), 'trac-tempenv') self.env = Environment(env_path, create=True) self.db = self.env.get_db_cnx() self.compmgr = ComponentManager() # init TicketTemplateModule self.tt = ttadmin.TicketTemplateModule(self.compmgr) setattr(self.tt, "env", self.env)
def _setup(self, configuration=None): configuration = configuration or \ '[TracPM]\nfields.estimate = estimatedhours\n' + \ 'date_format = %Y-%m-%d\n' + \ '[components]\ntracpm.* = enabled\n' instancedir = os.path.join(tempfile.gettempdir(), 'test-PM%d' % self.index[0]) self.index[0] += 1 if os.path.exists(instancedir): shutil.rmtree(instancedir, False) env = Environment(instancedir, create=True) open(os.path.join(os.path.join(instancedir, 'conf'), 'trac.ini'), 'a').write('\n' + configuration + '\n') return Environment(instancedir)
def _setup(self, configuration = None): configuration = configuration or '[ticket-custom]\nmycustomfield = text\nmycustomfield.label = My Custom Field\nmycustomfield.order = 1' instancedir = os.path.join(tempfile.gettempdir(), 'test-importer._preview') if os.path.exists(instancedir): shutil.rmtree(instancedir, False) env = Environment(instancedir, create=True) open(os.path.join(os.path.join(instancedir, 'conf'), 'trac.ini'), 'a').write('\n' + configuration + '\n') db = env.get_db_cnx() _exec(db.cursor(), "INSERT INTO permission VALUES ('anonymous', 'REPORT_ADMIN') ") _exec(db.cursor(), "INSERT INTO permission VALUES ('anonymous', 'IMPORT_EXECUTE') ") db.commit() ImporterTestCase.TICKET_TIME = 1190909220 return Environment(instancedir)
def get_number_of_tickets_per_cr(self, project): settings = get_current_registry().settings if not settings: return tracenvs = settings.get('penelope.trac.envs') for trac in project.tracs: env = Environment('%s/%s' % (tracenvs, trac.trac_name)) db = env.get_db_cnx() cursor = db.cursor() cursor.execute("""SELECT c.value as cr, count(t.id) AS number FROM ticket t INNER JOIN ticket_custom c ON (t.id = c.ticket AND c.name = 'customerrequest') group by cr;""") tickets = cursor.fetchall() db.rollback() return dict(tickets)
def _create_env(self, path, dburi): env = Environment(path, True, [('trac', 'database', dburi), ('trac', 'base_url', 'http://localhost/'), ('project', 'name', u'Pŕójéćŧ Ńáḿé')]) @env.with_transaction() def fn(db): cursor = db.cursor() cursor.execute("UPDATE system SET value='21' " "WHERE name='initial_database_version'") pages_dir = resource_filename('trac.wiki', 'default-pages') WikiAdmin(env).load_pages(pages_dir) att = Attachment(env, 'wiki', 'WikiStart') att.insert('filename.txt', StringIO('test'), 4) env.shutdown()
def test_version_file_empty(self): """TracError raised when environment version is empty.""" create_file(os.path.join(self.env.path, 'VERSION'), '') with self.assertRaises(TracError) as cm: Environment(self.env.path) self.assertEqual("Unknown Trac environment type ''", unicode(cm.exception))
def get_perm_groups(self,path): """Get array of permission groups (e.g. anonymous,authenticated) defined in the given environment. These 'users' should e.g. never be purged on cleanup """ users = [] env = Environment(path) sids = [] self.env.log.debug('Get users to keep from environment path %s' % (path,)) db = env.get_db_cnx() cursor = db.cursor() cursor.execute('SELECT DISTINCT username FROM permission WHERE username NOT IN (SELECT DISTINCT sid FROM session_attribute UNION SELECT DISTINCT sid FROM session UNION SELECT DISTINCT name FROM auth_cookie)') for row in cursor: users.append(row[0]) self.env.log.debug('Permission groups for %s: %s' % (path,','.join(users))) for user in env.config.getlist('user_sync','users_keep'): if not user in users: users.append(user) return users
def _controller_preview(self, req, cat, page, component): """ Compares source and destination wiki pages to let the user know which contents may be overriden during the import. """ # Data to be passed to view data = {'instance_id': '', 'instance_path': ''} # Choose between preconfigured and raw instance if req.args.get('wikiimport_instance_path'): data['instance_path'] = req.args.get('wikiimport_instance_path') source_env = Environment(data['instance_path']) elif req.args.get('wikiimport_instance_id'): data['instance_id'] = req.args.get('wikiimport_instance_id') source_env = self._get_instance_env(data['instance_id']) else: raise TracError('Please specify a path to a Trac environment.') # Get operations to be performed data['operations'] = self._get_page_operations(source_env, self.env) # This is required in order to display pages list alphabetically data['sorted_pages'] = data['operations'].keys() data['sorted_pages'].sort() # Add stylesheet to view add_stylesheet(req, 'wikiimport/css/wikiimport.css') # Render view if len(data['operations'].items()): return 'admin_wikiimport_preview.html', data else: return 'admin_wikiimport_preview_noops.html', data
def main(argv=None): if argv is None: argv = sys.argv try: try: opts, args = getopt.getopt(argv[1:], "e:n:s:h", ["env=", "name=", "size=", "help"]) except getopt.error, msg: raise Usage(msg) # global script variables env = None sprint_name = "Big Backlog" number_of_tickets = 1000 # option processing for option, value in opts: if option in ("-h", "--help"): raise Usage(help_message) if option in ("-e", "--env"): env = Environment(value) if option in ("-n", "--name"): sprint_name = value if option in ("-s", "--size"): number_of_tickets = int(value) # if no env is set we have to raise the error if not env: raise Usage("Please specify the environment path") print "Creating sprint <%s> with <%d> tickets." % (sprint_name, number_of_tickets) _create_sprint_with_backlog(env, sprint_name, number_of_tickets)
def reset_all_passwords(): """Generate random password for all the users and send e-mail to everyone. To customize messages, change a template reset_password_email.txt. Use with care! Test before using (e.g. with fakemail). """ from trac.core import ComponentMeta env = Environment(TRAC_ENV) def component_instance(name): for i in ComponentMeta._components: if i.__name__ == name: return i(env) raise ValueError("Component not found: " + name) mgr = component_instance("AccountManager") am = component_instance("AccountModule") class Req(): def __init__(self, **kw): self.authname = None self.method = 'POST' self.args = kw users = [i for i in mgr.get_users()] for sid in users: print "sending new password for " + sid req = Req(username=sid, email=sid + "@users.sourceforge.net") am._do_reset_password(req)
def main(*argv): parser = optparse.OptionParser( usage='Usage: %prog old-text new-text wiki-page ... trac-env', version='ReplaceInPage 1.0') parser.add_option('-d', '--debug', help='Activate debugging', action='store_true', default=False) (options, args) = parser.parse_args(list(argv[1:])) if len(args) < 3: parser.error("Not enough arguments") oldname = args[0] newname = args[1] wikipages = args[2:len(args) - 2] envpath = args[len(args - 1)] env = Environment(envpath) wiki_text_replace(env, oldtext, newtext, wikipages, username(), '127.0.0.1', debug=options.debug)
def __init__(self, project_name, path, db, host, user, password, append): self.env = Environment(path) self._append = append self._tracdb = self.env.get_db_cnx() self._tracdb.autocommit = False self._trac_cursor = self._tracdb.cursor() self._mantis_con = MySQLdb.connect(host=host, user=user, passwd=password, db=db, compress=1, cursorclass=MySQLdb.cursors.DictCursor, use_unicode=1) self._mantis_cursor = self._mantis_con.cursor() sql = "SELECT id FROM mantis_project_table WHERE name = %s" % (project_name) print sql self.mantisCursor().execute("SELECT id FROM mantis_project_table WHERE name = %s", (project_name)) result = self.mantisCursor().fetchall() if len(result) > 1: raise Exception("Ambiguous project name %s" % project_name) elif len(result) == 0: sql = """INSERT INTO mantis_project_table (name) VALUES (%s)""" % (project_name) print sql self.mantisCursor().execute("""INSERT INTO mantis_project_table (name) VALUES (%s)""" , (project_name)) self.mantisCommit() self._project_id = int(self.mantisCursor().lastrowid) else: self._project_id = int(result[0]['id']) self._bug_map = {} self._user_map = {} self._category_map = {}
def test_log_format(self): """Configure the log_format and log to a file at WARNING level.""" self.env.config.set('logging', 'log_type', 'file') self.env.config.set('logging', 'log_level', 'WARNING') self.env.config.set('logging', 'log_format', 'Trac[$(module)s] $(project)s: $(message)s') self.env.config.save() self.env.shutdown() self.env = Environment(self.env_path) # Reload environment self.env.log.warning("The warning message") with open(self.env.log_file_path) as f: log = f.readlines() self.assertEqual("Trac[env] My Project: The warning message\n", log[-1])
def main(argv=None): if argv is None: argv = sys.argv try: try: opts, args = getopt.getopt(argv[1:], "e:dh", ["env=", "delete", "help"]) except getopt.error, msg: raise Usage(msg) # global script variables env = None delete = False # option processing for option, value in opts: if option in ("-h", "--help"): raise Usage(help_message) if option in ("-e", "--env"): env = Environment(value) if option in ("-d", "--delete"): delete = True # if no env is set we have to raise the error if not env: raise Usage("Please specify the environment path") # Crete the data _create_demo_data(env, delete)
def test_config_argument(self): """Options contained in file specified by the --config argument are written to trac.ini. """ config_file = os.path.join(self.parent_dir, 'config.ini') create_file( config_file, """\ [the-plugin] option_a = 1 option_b = 2 [components] the_plugin.* = enabled [project] name = project2 """) rv, output = self.execute('initenv project1 sqlite:db/sqlite.db ' '--config=%s' % config_file) env = Environment(self.env_path) cfile = env.config.parser self.assertEqual(0, rv, output) self.assertEqual('1', cfile.get('the-plugin', 'option_a')) self.assertEqual('2', cfile.get('the-plugin', 'option_b')) self.assertEqual('enabled', cfile.get('components', 'the_plugin.*')) self.assertEqual('project1', cfile.get('project', 'name')) self.assertEqual('sqlite:db/sqlite.db', cfile.get('trac', 'database')) for (section, name), option in \ Option.get_registry(env.compmgr).iteritems(): if (section, name) not in \ (('trac', 'database'), ('project', 'name')): self.assertEqual(option.default, cfile.get(section, name))
def initialize_agilo(self, project_name, db_url, svn_repo, demo=False): try: self.do_initenv( '%s %s %s %s' % (project_name, db_url, 'svn', svn_repo or 'somewhere')) # Now add agilo and the template path env = Environment(self.envname) ac = AgiloConfig(env) if not svn_repo: # remove the fake from the config ac.change_option('repository_dir', '', 'trac') # sets the restric_owner option ac.change_option('restrict_owner', 'true', 'ticket') # this is also saving the config ac.enable_agilo() # update wiki wiki = WikiPage(env, name='WikiStart') wiki.text = agilo_wiki wiki.save('admin', 'Updated to Agilo', '127.0.0.1') # reset the env self.env_set(envname=self.envname, env=env) # Now initialize Agilo self.do_upgrade('upgrade --no-backup') # Now create the demo if needed if demo: try: from create_demo_data import _create_demo_data _create_demo_data(env) except ImportError, e: env.log.error(exception_to_unicode(e)) except: pass
def _init_env(self): self.__env = env = Environment(self.envname) # fix language according to env settings if has_babel: negotiated = get_console_locale(env) if negotiated: translation.activate(negotiated)
def __init__(self, path): self.env = Environment(path) self._db = self.env.get_db_cnx() self._db.autocommit = False self.loginNameCache = {} self.fieldNameCache = {} from trac.db.api import DatabaseManager self.using_postgres = DatabaseManager(self.env).connection_uri.startswith("postgres:")
def getMySQLEnvironment(opts): dburi = opts.mysql_uri env = Environment(opts.tracenv) env.config.set('trac', 'database', dburi) try: cnx = env.get_db_cnx() cur = cnx.cursor() cur.execute("select value from system where name = 'database_version'"); except ProgrammingError: cnx.rollback() DatabaseManager(env).init_db() DatabaseManager(env).shutdown() # if env.needs_upgrade(): # env.upgrade() return env
def test_migrate_to_sqlite_inplace(self): dburi = get_dburi() if dburi in ('sqlite::memory:', 'sqlite:db/trac.db'): dburi = 'sqlite:db/trac-migrate.db' self._create_env(self.src_path, dburi) self.src_env = Environment(self.src_path) src_options = self._get_options(self.src_env) src_records = self._get_all_records(self.src_env) self._migrate_inplace(self.src_env, 'sqlite:db/trac.db') self.src_env.shutdown() self.src_env = Environment(self.src_path) dst_options = self._get_options(self.src_env) dst_records = self._get_all_records(self.src_env) self.assertEqual({'name': 'initial_database_version', 'value': '21'}, dst_records['system']['initial_database_version']) self._compare_records(src_records, dst_records) self.assertEqual(src_options, dst_options)
def get_tracenv_users(self, path, userlist=''): """Get array of users defined in the specified environment having data assigned @param path path to the environment @param userlist comma separated list of users to restrict the result to (e.g. the users from the password file), each user enclosed in single quotes (for SQL) @return array [0..n] of string users """ env = Environment(path) sids = [] self.env.log.debug('Get users from %s' % (path,)) db = env.get_db_cnx() cursor = db.cursor() if userlist: cursor.execute("SELECT DISTINCT sid FROM session_attribute WHERE sid IN (%s) AND name != 'enabled'" % (userlist,)) else: cursor.execute("SELECT DISTINCT sid FROM session_attribute WHERE name != 'enabled'") for row in cursor: sids.append(row[0]) return sids
def get_tracenv_userdata(self, req, path, userlist=''): """Retrieve account data from the environment at the specified path @param path path to the environment @param userlist comma separated list of users to restrict the result to (e.g. the users from the password file), each user enclosed in single quotes (for SQL) @return array (empty array if the environment uses a different password file than the master env calling us) """ self.env.log.debug('Get user data from %s' % (path,)) data = {} env = Environment(path) # if this environment uses a different password file, we return an empty dataset if self.env.config.get('account-manager','password_file') != env.config.get('account-manager','password_file'): self.env.log.info('Password files do not match, skipping environment %s' % (path,)) return data perm = PermissionSystem(env) if not 'TRAC_ADMIN' in perm.get_user_permissions(req.perm.username): raise PermissionError db = env.get_db_cnx() cursor = db.cursor() sync_fields = self.env.config.getlist('user_sync','sync_fields') attr = "'"+"','".join(sync_fields)+"','email_verification_sent_to','email_verification_token'" self.env.log.debug('* Checking attributes: %s' % (attr,)) if userlist: cursor.execute("SELECT sid,name,value FROM session_attribute WHERE sid IN (%s) AND name IN (%s)" % (userlist,attr,)) else: cursor.execute("SELECT sid,name,value FROM session_attribute WHERE name IN (%s)" % (attr,)) for row in cursor: if not row[0] in data: data[row[0]] = {} data[row[0]][row[1]] = row[2] for sid in data.iterkeys(): no_data = True for att in sync_fields: if att in data[sid]: no_data = False break if no_data: self.env.log.debug('No data for %s in %s' % (sid,path,)) data[sid] = Null continue data[sid]['path'] = path cursor.execute("SELECT authenticated FROM session_attribute WHERE sid='%s'" % (sid,)) for row in cursor: data[sid]['authenticated'] = row[0] cursor.execute("SELECT datetime(last_visit,'unixepoch') AS last_visit FROM session WHERE sid='%s'" % (sid,)) for row in cursor: data[sid]['last_visit'] = row[0] return data
def test_migrate_to_sqlite_env(self): dburi = get_dburi() if dburi == 'sqlite::memory:': dburi = 'sqlite:db/trac.db' self._create_env(self.src_path, dburi) self.src_env = Environment(self.src_path) src_options = self._get_options(self.src_env) src_records = self._get_all_records(self.src_env) self._migrate(self.src_env, self.dst_path, 'sqlite:db/trac.db') self.dst_env = Environment(self.dst_path) dst_options = self._get_options(self.dst_env) dst_records = self._get_all_records(self.dst_env) self.assertEqual({'name': 'initial_database_version', 'value': '21'}, dst_records['system']['initial_database_version']) self._compare_records(src_records, dst_records) self.assertEqual(src_options, dst_options) att = Attachment(self.dst_env, 'wiki', 'WikiStart', 'filename.txt') self.assertEqual('test', read_file(att.path))
class TracDatabase(object): def __init__(self, path): self.env = Environment(path) self._db = self.env.get_db_cnx() def db(self): return self._db def hasTickets(self): c = self.db().cursor() c.execute("SELECT count(*) FROM Ticket") return int(c.fetchall()[0][0]) > 0 def setList(self, name, values): c = self.db().cursor() c.execute("DELETE FROM %s" % name) for v in values: print " inserting %s '%s'" % (name, v) c.execute("INSERT INTO " + name + " (name) VALUES (%s)", (v,)) self.db().commit() def setEnumList(self, name, values): c = self.db().cursor() c.execute("DELETE FROM enum WHERE type=%s", (name,)) for n, v in enumerate(values): print " inserting %s '%s'" % (name, v) c.execute("INSERT INTO enum (type, name, value) VALUES (%s,%s,%s)", (name, v, n)) self.db().commit() def clean(self): print "\nCleaning all tickets..." c = self.db().cursor() c.execute("DELETE FROM ticket_change") c.execute("DELETE FROM ticket") c.execute("DELETE FROM attachment") c.execute("DELETE FROM ticket_custom") self.db().commit() attachments_dir = os.path.join(os.path.normpath(self.env.path), "attachments") remove_recursively(attachments_dir) if not os.path.isdir(attachments_dir): os.mkdir(attachments_dir) def addAttachment(self, ticket_id, filename, datafile, filesize, author, description, upload_time): # copied from bugzilla2trac attachment = Attachment(self.env, 'ticket', ticket_id) attachment.author = author attachment.description = description attachment.insert(filename, datafile, filesize, upload_time) del attachment
def _create_a_project(self,owner,proj_name,proj_full_name,description,req): if "PROJECT_CREATE" in req.perm: inherit_file=self.env.config.get('projectsmanager','inherit_file') options=[ ('project','name',proj_full_name), ('project','descr',description), ('inherit','file',inherit_file) ] (projects_root_dir,_)=os.path.split(self.env.path) path = os.path.join(projects_root_dir,proj_name) self.log.debug('starting to create the environment.') Environment(path,True,options) env=Environment(path)# cmd: trac-admin path permission add XX TRAC_ADMIN cnx=env.get_db_cnx() cur=cnx.cursor() cur.execute("insert into permission values ('%s','TRAC_ADMIN');"%\ owner) cur.close() cnx.commit() cnx.close() else: pass
def getPostgreSQLEnvironment(opts): """ Create an Environment connected to the PostgreSQL database """ dburi = opts.pg_uri env = Environment(opts.tracenv) env.config.set('trac', 'database', dburi) try: cnx = env.get_db_cnx() cur = cnx.cursor() cur.execute("select value from system where name = 'database_version'"); except ProgrammingError: cnx.rollback() DatabaseManager(env).init_db() DatabaseManager(env).shutdown() for x in filter(None, [env.compmgr[cls] for cls in ComponentMeta._registry.get( IEnvironmentSetupParticipant, [])]): if isinstance(x, EnvironmentSetup): x.environment_created() if env.needs_upgrade(): env.upgrade() return env
class Script ( object ): def __init__(self): while self.find_and_remove_installs():pass (options,args) = parser.parse_args() if(len(args) == 0) : self.trac= p('Please type your trac path (or run this script passing it the path):') else: self.trac = args[0] print "Opening trac environment" self.env = Environment(self.trac) self.env.with_transaction() print "Removing T&E from trac env" self.find_and_remove_custom_vars() self.find_and_remove_ticket_change() self.find_and_remove_reports() self.remove_configuration() self.remove_system_keys() print "Done uninstalling" def execute_in_trans(self, *args): result = True c_sql =[None] c_params = [None] @self.env.with_transaction() def fn(db): try: cur = db.cursor() for sql, params in args: c_sql[0] = sql c_params[0] = params cur.execute(sql, params) except Exception, e : print 'There was a problem executing sql:%s \n \ with parameters:%s\nException:%s'%(c_sql[0], c_params[0], e); raise e return result
def _test_migrate_with_plugin_to_sqlite_env(self): self.src_env = Environment(self.src_path) self.assertTrue(self.src_env.needs_upgrade()) self.src_env.upgrade() self.assertFalse(self.src_env.needs_upgrade()) src_options = self._get_options(self.src_env) src_records = self._get_all_records(self.src_env) self._migrate(self.src_env, self.dst_path, 'sqlite:db/trac.db') self.dst_env = Environment(self.dst_path) self.assertFalse(self.dst_env.needs_upgrade()) self.assertFalse(os.path.exists(os.path.join(self.dst_path, 'log', 'created'))) self.assertTrue(os.path.exists(os.path.join(self.dst_path, 'log', 'upgraded'))) dst_options = self._get_options(self.dst_env) dst_records = self._get_all_records(self.dst_env) self.assertEqual({'name': 'initial_database_version', 'value': '21'}, dst_records['system']['initial_database_version']) self._compare_records(src_records, dst_records) self.assertEqual(src_options, dst_options) att = Attachment(self.dst_env, 'wiki', 'WikiStart', 'filename.txt') self.assertEqual('test', read_file(att.path))
class EnvironmentTestCase(unittest.TestCase): def setUp(self): env_path = os.path.join(tempfile.gettempdir(), "trac-tempenv") self.env = Environment(env_path, create=True) self.db = self.env.get_db_cnx() def tearDown(self): self.db.close() self.env.shutdown() # really closes the db connections shutil.rmtree(self.env.path) def test_get_version(self): """Testing env.get_version""" assert self.env.get_version() == db_default.db_version def test_get_known_users(self): """Testing env.get_known_users""" cursor = self.db.cursor() cursor.executemany("INSERT INTO session VALUES (%s,%s,0)", [("123", 0), ("tom", 1), ("joe", 1), ("jane", 1)]) cursor.executemany( "INSERT INTO session_attribute VALUES (%s,%s,%s,%s)", [ ("123", 0, "email", "*****@*****.**"), ("tom", 1, "name", "Tom"), ("tom", 1, "email", "*****@*****.**"), ("joe", 1, "email", "*****@*****.**"), ("jane", 1, "name", "Jane"), ], ) users = {} for username, name, email in self.env.get_known_users(self.db): users[username] = (name, email) assert not users.has_key("anonymous") self.assertEqual(("Tom", "*****@*****.**"), users["tom"]) self.assertEqual((None, "*****@*****.**"), users["joe"]) self.assertEqual(("Jane", None), users["jane"])