def test_insert_into_multiple_products(self): # UIDs are global, autoincremented # IDs are product-scoped, incremented in the SQL translator self.env = ProductEnvironment(self.global_env, self.default_product) tid = self._insert_ticket('hello kitty', reporter='admin') ticket = Ticket(self.env, tid) self.assertEqual(tid, 1) self.assertEqual(self._get_ticket_uid(tid), 1) self.assertEqual(ticket.id, tid) tid = self._insert_ticket('hello kitteh', reporter='admin') ticket = Ticket(self.env, tid) self.assertEqual(tid, 2) self.assertEqual(self._get_ticket_uid(tid), 2) self.assertEqual(ticket.id, tid) p2 = Product(self.global_env) p2.prefix = 'p2' p2.name = 'product, too' p2.owner = 'admin' p2.insert() self.env = ProductEnvironment(self.global_env, p2) tid = self._insert_ticket('hello catty', reporter='admin') ticket = Ticket(self.env, tid) self.assertEqual(tid, 1) self.assertEqual(self._get_ticket_uid(tid), 3) self.assertEqual(ticket.id, tid) tid = self._insert_ticket('hello ocelot', reporter='admin') ticket = Ticket(self.env, tid) self.assertEqual(tid, 2) self.assertEqual(self._get_ticket_uid(tid), 4) self.assertEqual(ticket.id, tid)
class ProductAttachmentTestCase(AttachmentTestCase, MultiproductTestCase): def setUp(self): try: AttachmentTestCase.setUp(self) except: self.global_env = self.env self.tearDown() raise else: self.global_env = global_env = self.env self._upgrade_mp(global_env) self._setup_test_log(global_env) self._load_product_from_data(global_env, self.default_product) self.env = ProductEnvironment(global_env, self.default_product) # Root folder for default product environment self.attachments_dir = os.path.join(self.global_env.path, 'products', self.default_product, 'files', 'attachments') def tearDown(self): if os.path.exists(self.global_env.path): shutil.rmtree(self.global_env.path) self.env.reset_db() def test_product_path_isolation(self): product_attachment = Attachment(self.env, 'ticket', '42') global_attachment = Attachment(self.global_env, 'ticket', '42') global_attachment.filename = product_attachment.filename = 'foo.txt' self.assertNotEqual(product_attachment.path, global_attachment.path)
def add_template_data(self, req, data, tickets): if isinstance(self.env, ProductEnvironment): super(ProductBatchModifyModule, self).add_template_data(req, data, tickets) return data['batch_modify'] = True data['query_href'] = req.session['query_href'] or req.href.query() tickets_by_product = {} for t in tickets: tickets_by_product.setdefault(t['product'], []).append(t) data['action_controls'] = [] global_env = ProductEnvironment.lookup_global_env(self.env) cache = {} for k, v in tickets_by_product.iteritems(): batch_module = cache.get(k or '') if batch_module is None: env = ProductEnvironment(global_env, k) if k else global_env cache[k] = batch_module = ProductBatchModifyModule(env) data['action_controls'] += batch_module._get_action_controls(req, v) batch_list_modes = [ {'name': _("add"), 'value': "+"}, {'name': _("remove"), 'value': "-"}, {'name': _("add / remove"), 'value': "+-"}, {'name': _("set to"), 'value': "="}, ] add_script_data(req, batch_list_modes=batch_list_modes, batch_list_properties=self._get_list_fields())
class ProductMilestoneTestCase(MilestoneTestCase, MultiproductTestCase): def setUp(self): self.global_env = self._setup_test_env(create_folder=True) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env) def tearDown(self): shutil.rmtree(self.global_env.path) self.global_env.reset_db() self.env = self.global_env = None def test_update_milestone(self): self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')") milestone = Milestone(self.env, 'Test') t1 = datetime(2001, 01, 01, tzinfo=utc) t2 = datetime(2002, 02, 02, tzinfo=utc) milestone.due = t1 milestone.completed = t2 milestone.description = 'Foo bar' milestone.update() self.assertEqual( [('Test', to_utimestamp(t1), to_utimestamp(t2), 'Foo bar', self.default_product)], self.env.db_query("SELECT * FROM milestone WHERE name='Test'"))
def setUp(self): self.global_env = self._setup_test_env(create_folder=True) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env)
def _get_product_info(self, product, href, resource, max_): penv = ProductEnvironment(self.env, product.prefix) results = [] # some queries return a list/tuple, some a generator, # hence count() to get the result length def count(iter_): try: return len(iter_) except TypeError: return sum(1 for _ in iter_) query = resource['type'].select(penv) for q in itertools.islice(query, max_): q.url = href(resource['name'], q.name) \ if resource.get('hrefurl') \ else Query.from_string(penv, '%s=%s&%s&col=%s' % (resource['name'], q.name, self.COMMON_QUERY, resource['name']) ).get_href(href) q.ticket_count = penv.db_query(""" SELECT COUNT(*) FROM ticket WHERE ticket.%s='%s' AND ticket.status <> 'closed' """ % (resource['name'], q.name))[0][0] results.append(q) # add a '(No <milestone/component/version>)' entry if there are # tickets without an assigned resource in the product ticket_count = penv.db_query( """SELECT COUNT(*) FROM ticket WHERE %s='' AND status <> 'closed'""" % (resource['name'],))[0][0] if ticket_count != 0: q = resource['type'](penv) q.name = '(No %s)' % (resource['name'],) q.url = Query.from_string(penv, 'status=!closed&col=id&col=summary&col=owner' '&col=status&col=priority&order=priority&%s=' % (resource['name'],) ).get_href(href) q.ticket_count = ticket_count results.append(q) results.sort(key=lambda x: x.ticket_count, reverse=True) # add a link to the resource list if there are # more than max resources defined if count(query) > max_: q = resource['type'](penv) q.name = _('... more') q.ticket_count = None q.url = href(resource['name']) if resource.get('hrefurl') \ else href.dashboard() results.append(q) return results
def setUp(self): self._mp_setup() self.env.abs_href = Href('http://globalenv.com/trac.cgi') url_pattern = getattr( getattr(self, self._testMethodName).im_func, 'product_base_url', '') self.env.config.set('multiproduct', 'product_base_url', url_pattern) self.env.config.set('trac', 'base_url', 'http://globalenv.com/trac.cgi') self.product_env = ProductEnvironment(self.env, self.default_product)
def setUp(self): self._mp_setup() self.global_env = self.env self._load_product_from_data(self.global_env, u'xü') self.env = ProductEnvironment(self.global_env, self.default_product) self.env1 = ProductEnvironment(self.global_env, u'xü') self._load_default_data(self.global_env) self._load_default_data(self.env1) # Enable product system component in product context self.env.enable_component(MultiProductSystem)
def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) self.global_env.enable_component(TestRequestSpy) self.env.enable_component(TestRequestSpy) TestRequestSpy(self.global_env).testMatch = self._assert_product_match PermissionSystem(self.global_env).grant_permission( 'testuser', 'PRODUCT_CREATE') PermissionSystem(self.global_env).grant_permission( 'testuser', 'PRODUCT_VIEW') PermissionSystem(self.global_env).grant_permission( 'testuser', 'PRODUCT_MODIFY')
def test_env_isolation(self): global_env = self.global_env env = self.env self._load_product_from_data(self.global_env, 'tp2') env1 = ProductEnvironment(self.global_env, 'tp2') global_store = perm.DefaultPermissionStore(global_env) store = perm.DefaultPermissionStore(env) store1 = perm.DefaultPermissionStore(env1) global_env.db_transaction.executemany( "INSERT INTO permission VALUES (%s,%s)", [('dev', 'WIKI_MODIFY'), ('dev', 'REPORT_ADMIN'), ('john', 'dev')]) env.db_transaction.executemany( "INSERT INTO permission VALUES (%s,%s)", [('dev', 'WIKI_VIEW'), ('dev', 'REPORT_VIEW'), ('john', 'dev')]) env1.db_transaction.executemany( "INSERT INTO permission VALUES (%s,%s)", [('dev', 'TICKET_CREATE'), ('dev', 'MILESTONE_VIEW'), ('john', 'dev')]) self.assertEquals(['REPORT_ADMIN', 'WIKI_MODIFY'], sorted(global_store.get_user_permissions('john'))) self.assertEquals(['REPORT_VIEW', 'WIKI_VIEW'], sorted(store.get_user_permissions('john'))) self.assertEquals(['MILESTONE_VIEW', 'TICKET_CREATE'], sorted(store1.get_user_permissions('john')))
def product_admincmd_mgr(self, prefix): try: product_env = ProductEnvironment.lookup_env(self.env, prefix) except LookupError: raise AdminCommandError('Unknown product %s' % (prefix, )) else: return AdminCommandManager(product_env)
def find_ticket(self, ticket_spec): ticket = None m = re.match(r'#?(?P<tid>\d+)', ticket_spec) if m: tid = m.group('tid') try: ticket = Ticket(self.env, tid) except ResourceNotFound: # ticket not found in current product, try all other products for p in Product.select(self.env): if p.prefix != self.env.product.prefix: # TODO: check for PRODUCT_VIEW permissions penv = ProductEnvironment(self.env.parent, p.prefix) try: ticket = Ticket(penv, tid) except ResourceNotFound: pass else: break # ticket still not found, use fallback for <prefix>:ticket:<id> syntax if ticket is None: try: resource = ResourceIdSerializer.get_resource_by_id(ticket_spec) ticket = self._create_ticket_by_full_id(resource) except: raise NoSuchTicketError return ticket
def setUp(self): self._mp_setup(create_folder=True) self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) # Random component class self.component_class = self.DummyAdminCommand
def product_admincmd_mgr(self, prefix): try: product_env = ProductEnvironment.lookup_env(self.env, prefix) except LookupError: raise AdminCommandError('Unknown product %s' % (prefix,)) else: return AdminCommandManager(product_env)
def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) # Product name inserted in RSS feed self.env.product._data['name'] = 'My Project' self.env.config.set( 'trac', 'templates_dir', os.path.join(os.path.dirname(self.env.path), 'templates')) self.ticket_module = ProductTicketModule(self.env) self.mimeview = Mimeview(self.env) self.req = Mock(base_path='/trac.cgi', path_info='', href=Href('/trac.cgi'), chrome={'logo': {}}, abs_href=Href('http://example.org/trac.cgi'), environ={}, perm=[], authname='-', args={}, tz=None, locale='', session=None, form_token=None)
def test_get_tickets(self): for pdata in ( {'prefix': 'p2', 'name':'product, too', 'description': ''}, {'prefix': 'p3', 'name':'strike three', 'description': ''}, ): num_tickets = 5 product = Product(self.global_env) product._data.update(pdata) product.insert() self.env = ProductEnvironment(self.global_env, product) for i in range(num_tickets): ticket = Ticket(self.env) ticket['summary'] = 'hello ticket #%s-%d' % (product.prefix, i) ticket['reporter'] = 'admin' tid = ticket.insert() # retrieve tickets using both global and product scope tickets_from_global = [(t['product'], t['id']) for t in Product.get_tickets(self.global_env, product.prefix)] self.assertEqual(len(tickets_from_global), num_tickets) tickets_from_product = [(t['product'], t['id']) for t in Product.get_tickets(self.env)] self.assertEqual(len(tickets_from_product), num_tickets) # both lists should contain same elements intersection = set(tickets_from_global) & set(tickets_from_product) self.assertEqual(len(intersection), num_tickets)
def resource_created(self, resource, context): import trac.db_default from multiproduct.env import EnvironmentStub # Don't populate product database when running from within test # environment stub as test cases really don't expect that ... if isinstance(self.env, EnvironmentStub): return product = resource self.log.debug("Adding product info (%s) to tables:" % product.prefix) with self.env.db_direct_transaction as db: # create the default entries for this Product from defaults for table in trac.db_default.get_data(db): if not table[0] in self.PRODUCT_POPULATE_TABLES: continue self.log.debug(" -> %s" % table[0]) cols = table[1] + ('product', ) rows = [p + (product.prefix, ) for p in table[2]] db.executemany( "INSERT INTO %s (%s) VALUES (%s)" % (table[0], ','.join(cols), ','.join(['%s' for c in cols])), rows) # Import default pages in product wiki wikiadmin = WikiAdmin(ProductEnvironment(self.env, product.prefix)) pages = ('TitleIndex', 'RecentChanges', 'InterTrac', 'InterWiki') for page in pages: filename = resource_filename('trac.wiki', 'default-pages/' + page) wikiadmin.import_page(filename, page)
def create(self, req, summary, description, attributes={}, notify=False): """ Create a new ticket, returning the ticket ID. PS: Borrowed from XmlRpcPlugin. """ if 'product' in attributes: env = self.env.parent or self.env if attributes['product']: env = ProductEnvironment(env, attributes['product']) else: env = self.env t = Ticket(env) t['summary'] = summary t['description'] = description t['reporter'] = req.authname for k, v in attributes.iteritems(): t[k] = v t['status'] = 'new' t['resolution'] = '' t.insert() if notify: try: tn = TicketNotifyEmail(env) tn.notify(t, newticket=True) except Exception, e: self.log.exception("Failure sending notification on creation " "of ticket #%s: %s" % (t.id, e))
def setUp(self): self._prepare_env() self._setup_test_log(self.global_env) formatter.WikiTestCase.setUp(self) if self.context.req: self.context.req.session = FakeSession() if self.mpctx: candidates = set( self.mpctx.get('load_products', []) + [self.mpctx.get('main_product')]) candidates -= set( [self.default_product, None, self.mpctx.get('setup_product')]) for prefix in candidates: self._load_product_from_data(self.env, prefix) prefix = self.mpctx.get('main_product', NotImplemented) if prefix is None: self.env = self.global_env elif prefix is not NotImplemented \ and (self.env is self.global_env or prefix != self.env.product.prefix): self.env = ProductEnvironment(self.global_env, prefix) # Enable multi-product components self.env.config.set('components', 'multiproduct.*', 'enabled')
def setUp(self): self.global_env = self._setup_test_env() self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.env, self.default_product) self.env = ProductEnvironment(self.env, self.default_product) self.store = perm.DefaultPermissionStore(self.env)
def process_request(self, req): product = req.args.get('product') fields_to_update = req.args.get('fields_to_update[]'); env = ProductEnvironment(self.env.parent, req.args.get('product')) ticket_fields = TicketSystem(env).get_ticket_fields() data = dict([f['name'], f['options']] for f in ticket_fields if f['type'] == 'select' and f['name'] in fields_to_update) req.send(to_json(data), 'application/json')
def setUp(self): try: AttachmentTestCase.setUp(self) except: self.global_env = self.env self.tearDown() raise else: self.global_env = global_env = self.env self._upgrade_mp(global_env) self._setup_test_log(global_env) self._load_product_from_data(global_env, self.default_product) self.env = ProductEnvironment(global_env, self.default_product) # Root folder for default product environment self.attachments_dir = os.path.join(self.global_env.path, 'products', self.default_product, 'files', 'attachments')
class ProductResourceTestCase(MultiproductTestCase): def setUp(self): self._mp_setup() self.global_env = self.env self._load_product_from_data(self.global_env, u'xü') self.env = ProductEnvironment(self.global_env, self.default_product) self.env1 = ProductEnvironment(self.global_env, u'xü') self._load_default_data(self.global_env) self._load_default_data(self.env1) # Enable product system component in product context self.env.enable_component(MultiProductSystem) def tearDown(self): self.global_env.reset_db() self.global_env = self.env = None
def setUp(self): self._mp_setup() self.env.path = '/path/to/env' self.env.abs_href = Href('http://globalenv.com/trac.cgi') url_pattern = getattr(getattr(self, self._testMethodName).im_func, 'product_base_url', '') self.env.config.set('multiproduct', 'product_base_url', url_pattern) self.env.config.set('trac', 'base_url', 'http://globalenv.com/trac.cgi') self.product_env = ProductEnvironment(self.env, self.default_product)
def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env) self.env.config.set('ticket-custom', 'foo', 'text') self.env.config.set('ticket-custom', 'cbon', 'checkbox') self.env.config.set('ticket-custom', 'cboff', 'checkbox')
def check_permission(self, doc, context): product, doctype, id = doc['product'], doc['type'], doc['id'] username = context.req.authname env = self.env if product: env = ProductEnvironment(self.env, product) perm = PermissionSystem(env) action = self._required_permissions[doctype] return perm.check_permission(action, username, id)
def setUp(self): self.global_env = self._setup_test_env(create_folder=True, path=os.path.join( tempfile.gettempdir(), 'trac-tempenv')) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product)
def post_process_request(self, req, template, data, content_type): """Append necessary ticket data """ try: tm = self._get_ticket_module() except TracError: # no ticket module so no create ticket button return template, data, content_type if (template, data, content_type) != (None,) * 3: # TODO: Check ! if data is None: data = {} dum_req = dummy_request(self.env) dum_req.perm = req.perm ticket = Ticket(self.env) tm._populate(dum_req, ticket, False) all_fields = dict([f['name'], f] for f in tm._prepare_fields(dum_req, ticket) if f['type'] == 'select') product_field = all_fields.get('product') if product_field: # When at product scope, set the default selection to the # product at current scope. When at global scope the default # selection is determined by [ticket] default_product if self.env.product and \ self.env.product.prefix in product_field['options']: product_field['value'] = self.env.product.prefix # Transform the options field to dictionary of product # attributes and filter out products for which user doesn't # have TICKET_CREATE permission product_field['options'] = [ dict(value=p, new_ticket_url=dum_req.href.products(p, 'newticket'), description=ProductEnvironment.lookup_env(self.env, p) .product.name ) for p in product_field['options'] if req.perm.has_permission('TICKET_CREATE', Neighborhood('product', p) .child(None, None))] else: msg = _("Missing ticket field '%(field)s'.", field='product') if ProductTicketModule is not None and \ self.env[ProductTicketModule] is not None: # Display warning alert to users add_warning(req, msg) else: # Include message in logs since this might be a failure self.log.warning(msg) data['qct'] = { 'fields': [all_fields[k] for k in self.qct_fields if k in all_fields], 'hidden_fields': [all_fields[k] for k in all_fields.keys() if k not in self.qct_fields] } return template, data, content_type
def setUp(self): self.global_env = self._setup_test_env(create_folder=False) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock()
def post_process_request(self, req, template, data, content_type): """Append necessary ticket data """ try: tm = self._get_ticket_module() except TracError: # no ticket module so no create ticket button return template, data, content_type if (template, data, content_type) != (None, ) * 3: # TODO: Check ! if data is None: data = {} req = dummy_request(self.env) ticket = Ticket(self.env) tm._populate(req, ticket, False) all_fields = dict([f['name'], f] for f in tm._prepare_fields(req, ticket) if f['type'] == 'select') product_field = all_fields['product'] if product_field: if self.env.product: product_field['value'] = self.env.product.prefix else: # Global scope, now check default_product_prefix is valid default_prefix = self.config.get('multiproduct', 'default_product_prefix') try: ProductEnvironment.lookup_env(self.env, default_prefix) except LookupError: product_field['value'] = product_field['options'][0] else: product_field['value'] = default_prefix data['qct'] = { 'fields': [all_fields[k] for k in self.qct_fields if k in all_fields], 'hidden_fields': [ all_fields[k] for k in all_fields.keys() if k not in self.qct_fields ] } return template, data, content_type
def test_creating_new_product_calls_environment_created(self): self._enable_component(DummyPlugin) self._enable_multiproduct() self.env.upgrade() prod = Product(self.env) prod.update_field_dict(dict(prefix='p1')) ProductEnvironment(self.env, prod, create=True) with self.env.db_direct_transaction as db: db('SELECT * FROM "p1_dummy_table"')
def env(self): env = getattr(self, '_env', None) if env is None: self.global_env = self._setup_test_env(enable=[AuthzSourcePolicy]) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self._env = env = ProductEnvironment(self.global_env, self.default_product) return env
def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) self.global_env.enable_component(TestRequestSpy) self.env.enable_component(TestRequestSpy) TestRequestSpy(self.global_env).testMatch = self._assert_product_match PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_CREATE') PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_VIEW') PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_MODIFY')
def test_can_find_ticket_by_product_and_id(self): """ Can find ticket given #prefix-id""" product2 = "tp2" self._load_product_from_data(self.global_env, product2) p2_env = ProductEnvironment(self.global_env, product2) t1 = self._insert_and_load_ticket_with_env(p2_env, "T1") trs = TicketRelationsSpecifics(self.env) ticket = trs.find_ticket("#%s-%d" % (product2, t1.id)) self.assertEqual(ticket.id, 1)
def post_process_request(self, req, template, data, content_type): """Append necessary ticket data """ try: tm = self._get_ticket_module() except TracError: # no ticket module so no create ticket button return template, data, content_type if (template, data, content_type) != (None,) * 3: # TODO: Check ! if data is None: data = {} req = dummy_request(self.env) ticket = Ticket(self.env) tm._populate(req, ticket, False) all_fields = dict([f['name'], f] for f in tm._prepare_fields(req, ticket) if f['type'] == 'select') product_field = all_fields['product'] if product_field: if self.env.product: product_field['value'] = self.env.product.prefix else: # Global scope, now check default_product_prefix is valid default_prefix = self.config.get('multiproduct', 'default_product_prefix') try: ProductEnvironment.lookup_env(self.env, default_prefix) except LookupError: product_field['value'] = product_field['options'][0] else: product_field['value'] = default_prefix data['qct'] = { 'fields': [all_fields[k] for k in self.qct_fields if k in all_fields], 'hidden_fields': [all_fields[k] for k in all_fields.keys() if k not in self.qct_fields] } return template, data, content_type
def _process_doc(self, doc): ui_doc = dict(doc) if doc.get('product'): env = ProductEnvironment(self.env, doc['product']) product_href = ProductEnvironment.resolve_href(env, self.env) ui_doc["href"] = product_href(doc['type'], doc['id']) else: ui_doc["href"] = self.env.href(doc['type'], doc['id']) ui_doc['title'] = str(doc['type'] + ": " + doc['_stored_name']).title() return ui_doc
def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'multiproduct.*']) self.env.path = tempfile.mkdtemp(prefix='bh-product-tempenv-') self.mpsystem = MultiProductSystem(self.env) try: self.mpsystem.upgrade_environment(self.env.db_transaction) except self.env.db_exc.OperationalError: # table remains but database version is deleted pass self.listener = self._enable_resource_change_listener() self.default_data = {'prefix':self.INITIAL_PREFIX, 'name':self.INITIAL_NAME, 'description':self.INITIAL_DESCRIPTION} self.global_env = self.env self.product = Product(self.env) self.product._data.update(self.default_data) self.product.insert()
def _process_doc(self, doc): ui_doc = dict(doc) if doc.get('product'): env = ProductEnvironment(self.env, doc['product']) product_href = ProductEnvironment.resolve_href(env, self.env) # pylint: disable=too-many-function-args ui_doc["href"] = product_href(doc['type'], doc['id']) else: ui_doc["href"] = self.req.href(doc['type'], doc['id']) if doc['content']: ui_doc['content'] = shorten_result(doc['content']) if doc['time']: ui_doc['date'] = user_time(self.req, format_datetime, doc['time']) is_free_text_view = self.view is None if is_free_text_view: participant = self.allowed_participants[doc['type']] ui_doc['title'] = participant.format_search_results(doc) return ui_doc
def render_widget(self, name, context, options): """Gather product list and render data in compact view """ data = {} req = context.req title = '' params = ('max', 'cols') max_, cols = self.bind_params(name, options, *params) if not isinstance(self.env, ProductEnvironment): for p in Product.select(self.env): if 'PRODUCT_VIEW' in req.perm(Neighborhood('product', p.prefix)): penv = ProductEnvironment(self.env, p.prefix) phref = ProductEnvironment.resolve_href(penv, self.env) for resource in ( {'type': Milestone, 'name': 'milestone', 'hrefurl': True}, {'type': Component, 'name': 'component'}, {'type': Version, 'name': 'version'}, ): setattr(p, resource['name'] + 's', self._get_product_info(p, phref, resource, max_)) p.owner_link = Query.from_string(self.env, 'status!=closed&col=id&col=summary&col=owner' '&col=status&col=priority&order=priority' '&group=product&owner=%s' % (p._data['owner'] or '', ) ).get_href(phref) p.href = phref() data.setdefault('product_list', []).append(p) title = _('Products') data['colseq'] = itertools.cycle(xrange(cols - 1, -1, -1)) if cols \ else itertools.repeat(1) return 'widget_product.html', { 'title': title, 'data': data, 'ctxtnav': [tag.a(_('More'), href=req.href('products'))], }, context
class ProductTestCase(unittest.TestCase): """Unit tests covering the Product model""" INITIAL_PREFIX = 'tp' INITIAL_NAME = 'test project' INITIAL_DESCRIPTION = 'a test project' def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'multiproduct.*']) self.env.path = tempfile.mkdtemp(prefix='bh-product-tempenv-') self.mpsystem = MultiProductSystem(self.env) try: self.mpsystem.upgrade_environment(self.env.db_transaction) except self.env.db_exc.OperationalError: # table remains but database version is deleted pass self.listener = self._enable_resource_change_listener() self.default_data = {'prefix':self.INITIAL_PREFIX, 'name':self.INITIAL_NAME, 'description':self.INITIAL_DESCRIPTION} self.global_env = self.env self.product = Product(self.env) self.product._data.update(self.default_data) self.product.insert() def tearDown(self): shutil.rmtree(self.env.path) self.env.reset_db() def _enable_resource_change_listener(self): listener = TestResourceChangeListener(self.env) listener.resource_type = Product listener.callback = self.listener_callback return listener def listener_callback(self, action, resource, context, old_values = None): # pylint: disable=unused-argument # pylint: disable=attribute-defined-outside-init self.prefix = resource.prefix self.name = resource.name self.description = resource.description def test_set_table_field(self): """tests that table.field style update works""" test = {'prefix': 'td', 'name': 'test field access', 'description': 'product to test field setting'} product = Product(self.env) # attempt to set the fields from the data product.prefix = test['prefix'] product.name = test['name'] product.description = test['description'] self.assertEqual(product._data['prefix'], test['prefix']) self.assertEqual(product._data['name'], test['name']) self.assertEqual(product._data['description'], test['description']) def test_select(self): """tests that select can search Products by fields""" p2_data = {'prefix':'tp2', 'name':'test project 2', 'description':'a different test project'} p3_data = {'prefix':'tp3', 'name':'test project 3', 'description':'test project'} product2 = Product(self.env) product2._data.update(p2_data) product3 = Product(self.env) product3._data.update(p3_data) product2.insert() product3.insert() products = list(Product.select(self.env, where={'prefix':'tp'})) self.assertEqual(1, len(products)) products = list(Product.select(self.env, where={'name':'test project'})) self.assertEqual(1, len(products)) products = list(Product.select(self.env, where={'prefix':'tp3', 'name':'test project 3'})) self.assertEqual(1, len(products)) def test_update(self): """tests that we can use update to push data to the database""" product = list(Product.select(self.env, where={'prefix':'tp'}))[0] self.assertEqual('test project', product._data['name']) new_data = {'prefix':'tp', 'name':'updated', 'description':'nothing'} product._data.update(new_data) product.update() comp_product = list(Product.select(self.env, where={'prefix':'tp'}))[0] self.assertEqual('updated', comp_product._data['name']) def test_update_key_change(self): """tests that we raise an error for attempting to update key fields""" bad_data = {'prefix':'tp0', 'name':'update', 'description':'nothing'} product = list(Product.select(self.env, where={'prefix':'tp'}))[0] product._data.update(bad_data) self.assertRaises(TracError, product.update) def test_insert(self): """test saving new Product""" data = {'prefix':'new', 'name':'new', 'description':'new'} product = Product(self.env) product._data.update(data) product.insert() check_products = list(Product.select(self.env, where={'prefix':'new'})) self.assertEqual(product._data['prefix'], check_products[0]._data['prefix']) self.assertEqual(1, len(check_products)) def test_insert_duplicate_key(self): """test attempted saving of Product with existing key fails""" dupe_key_data = {'prefix':'tp', 'name':'dupe', 'description':'dupe primary key'} product2 = Product(self.env) product2._data.update(dupe_key_data) self.assertRaises(TracError, product2.insert) def test_delete(self): """test that we are able to delete Products""" product = list(Product.select(self.env, where={'prefix':'tp'}))[0] product.delete() post = list(Product.select(self.env, where={'prefix':'tp'})) self.assertEqual(0, len(post)) def test_delete_twice(self): """test that we error when deleting twice on the same key""" product = list(Product.select(self.env, where={'prefix':'tp'}))[0] product.delete() self.assertRaises(TracError, product.delete) def test_field_data_get(self): """tests that we can use table.field syntax to get to the field data""" prefix = self.default_data['prefix'] name = self.default_data['name'] description = self.default_data['description'] product = list(Product.select(self.env, where={'prefix':prefix}))[0] self.assertEqual(prefix, product.prefix) self.assertEqual(name, product.name) self.assertEqual(description, product.description) def test_field_set(self): """tests that we can use table.field = something to set field data""" prefix = self.default_data['prefix'] product = list(Product.select(self.env, where={'prefix':prefix}))[0] new_description = 'test change of description' product.description = new_description self.assertEqual(new_description, product.description) def test_missing_unique_fields(self): """ensure that that insert method works when _meta does not specify unique fields when inserting more than one ProductResourceMap instances """ class TestModel(ModelBase): """A test model with no unique_fields""" _meta = {'table_name': 'bloodhound_testmodel', 'object_name': 'TestModelObject', 'key_fields': ['id',], 'non_key_fields': ['value'], 'unique_fields': [],} from trac.db import DatabaseManager schema = [TestModel._get_schema(), ] with self.env.db_transaction as db: db_connector, dummy = DatabaseManager(self.env)._get_connector() for table in schema: for statement in db_connector.to_sql(table): db(statement) structure = dict([(table.name, [col.name for col in table.columns]) for table in schema]) tm1 = TestModel(self.env) tm1._data.update({'id':1, 'value':'value1'}) tm1.insert() tm2 = TestModel(self.env) tm2._data.update({'id':2, 'value':'value2'}) tm2.insert() def test_change_listener_created(self): self.assertEqual('created', self.listener.action) self.assertIsInstance(self.listener.resource, Product) self.assertEqual(self.INITIAL_PREFIX, self.prefix) self.assertEqual(self.INITIAL_NAME, self.name) self.assertEqual(self.INITIAL_DESCRIPTION, self.description) def test_change_listener_changed(self): CHANGED_NAME = "changed name" self.product.name = CHANGED_NAME self.product.update() self.assertEqual('changed', self.listener.action) self.assertIsInstance(self.listener.resource, Product) self.assertEqual(CHANGED_NAME, self.name) self.assertEqual({"name":self.INITIAL_NAME}, self.listener.old_values) def test_change_listener_deleted(self): self.product.delete() self.assertEqual('deleted', self.listener.action) self.assertIsInstance(self.listener.resource, Product) self.assertEqual(self.INITIAL_PREFIX, self.prefix) def test_get_tickets(self): for pdata in ( {'prefix': 'p2', 'name':'product, too', 'description': ''}, {'prefix': 'p3', 'name':'strike three', 'description': ''}, ): num_tickets = 5 product = Product(self.global_env) product._data.update(pdata) product.insert() self.env = ProductEnvironment(self.global_env, product) for i in range(num_tickets): ticket = Ticket(self.env) ticket['summary'] = 'hello ticket #%s-%d' % (product.prefix, i) ticket['reporter'] = 'admin' tid = ticket.insert() # retrieve tickets using both global and product scope tickets_from_global = [(t['product'], t['id']) for t in Product.get_tickets(self.global_env, product.prefix)] self.assertEqual(len(tickets_from_global), num_tickets) tickets_from_product = [(t['product'], t['id']) for t in Product.get_tickets(self.env)] self.assertEqual(len(tickets_from_product), num_tickets) # both lists should contain same elements intersection = set(tickets_from_global) & set(tickets_from_product) self.assertEqual(len(intersection), num_tickets)
class ProductEnvHrefTestCase(MultiproductTestCase): """Assertions for resolution of product environment's base URL [https://issues.apache.org/bloodhound/wiki/Proposals/BEP-0003 BEP 3] """ def product_base_url(url_template): def decorator(f): f.product_base_url = url_template return f return decorator def setUp(self): self._mp_setup() self.env.path = '/path/to/env' self.env.abs_href = Href('http://globalenv.com/trac.cgi') url_pattern = getattr(getattr(self, self._testMethodName).im_func, 'product_base_url', '') self.env.config.set('multiproduct', 'product_base_url', url_pattern) self.env.config.set('trac', 'base_url', 'http://globalenv.com/trac.cgi') self.product_env = ProductEnvironment(self.env, self.default_product) def tearDown(self): # Release reference to transient environment mock object if self.env is not None: try: self.env.reset_db() except OperationalError: # "Database not found ...", # "OperationalError: no such table: system" or the like pass self.env = None self.product_env = None @product_base_url('http://$(prefix)s.domain.tld/') def test_href_subdomain(self): """Test product sub domain base URL """ self.assertEqual('/', self.product_env.href()) self.assertEqual('http://tp1.domain.tld', self.product_env.abs_href()) @product_base_url('/path/to/bloodhound/$(prefix)s') def test_href_sibling_paths(self): """Test product base URL at sibling paths """ self.assertEqual('/trac.cgi/path/to/bloodhound/tp1', self.product_env.href()) self.assertEqual('http://globalenv.com/trac.cgi/path/to/bloodhound/tp1', self.product_env.abs_href()) @product_base_url('/$(envname)s/$(prefix)s') def test_href_inherit_sibling_paths(self): """Test product base URL at sibling paths inheriting configuration. """ self.assertEqual('/trac.cgi/env/tp1', self.product_env.href()) self.assertEqual('http://globalenv.com/trac.cgi/env/tp1', self.product_env.abs_href()) @product_base_url('') def test_href_default(self): """Test product base URL is to a default """ self.assertEqual('/trac.cgi/products/tp1', self.product_env.href()) self.assertEqual('http://globalenv.com/trac.cgi/products/tp1', self.product_env.abs_href()) @product_base_url('/products/$(prefix)s') def test_href_embed(self): """Test default product base URL /products/prefix """ self.assertEqual('/trac.cgi/products/tp1', self.product_env.href()) self.assertEqual('http://globalenv.com/trac.cgi/products/tp1', self.product_env.abs_href()) @product_base_url('http://$(envname)s.tld/bh/$(prefix)s') def test_href_complex(self): """Test complex product base URL """ self.assertEqual('/bh/tp1', self.product_env.href()) self.assertEqual('http://env.tld/bh/tp1', self.product_env.abs_href()) @product_base_url('http://$(prefix)s.$(envname)s.tld/') def test_product_href_uses_multiproduct_product_base_url(self): """Test that [multiproduct] product_base_url is used to compute abs_href for the product environment when [trac] base_url for the product environment is an empty string (the default). """ # Global URLs self.assertEqual('http://globalenv.com/trac.cgi', self.env.base_url) self.assertEqual('/trac.cgi', self.env.href()) self.assertEqual('http://globalenv.com/trac.cgi', self.env.abs_href()) # Product URLs self.assertEqual('', self.product_env.base_url) self.assertEqual('/', self.product_env.href()) self.assertEqual('http://tp1.env.tld', self.product_env.abs_href()) @product_base_url('http://$(prefix)s.$(envname)s.tld/') def test_product_href_uses_products_base_url(self): """Test that [trac] base_url for the product environment is used to compute abs_href for the product environment when [trac] base_url for the product environment is different than [trac] base_url for the global environment. """ self.product_env.config.set('trac', 'base_url', 'http://productenv.com') self.product_env.config.save() self.assertEqual('http://productenv.com', self.product_env.base_url) self.assertEqual('/', self.product_env.href()) self.assertEqual('http://productenv.com', self.product_env.abs_href()) @product_base_url('http://$(prefix)s.$(envname)s.tld/') def test_product_href_global_and_product_base_urls_same(self): """Test that [multiproduct] product_base_url is used to compute abs_href for the product environment when [trac] base_url is the same for the product and global environment. """ self.product_env.config.set('trac', 'base_url', self.env.config.get('trac', 'base_url')) self.product_env.config.save() self.assertEqual('', self.product_env.base_url) self.assertEqual('/', self.product_env.href()) self.assertEqual('http://tp1.env.tld', self.product_env.abs_href()) product_base_url = staticmethod(product_base_url)
class ProductMilestoneTestCase(MilestoneTestCase, MultiproductTestCase): def setUp(self): self.global_env = self._setup_test_env(create_folder=True) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env) def tearDown(self): shutil.rmtree(self.global_env.path) self.global_env.reset_db() self.env = self.global_env = None @unittest.skipUnless(threading, 'Threading required for test') def test_milestone_threads(self): """ Ensure that in threaded (e.g. mod_wsgi) situations, we get an accurate list of milestones from Milestone.list The basic strategy is: thread-1 requests a list of milestones thread-2 adds a milestone thread-1 requests a new list of milestones To pass, thread-1 should have a list of milestones that matches those that are in the database. """ lock = threading.RLock() results = [] # two events to coordinate the workers and ensure that the threads # alternate appropriately e1 = threading.Event() e2 = threading.Event() def task(add): """the thread task - either we are discovering or adding events""" with lock: env = ProductEnvironment(self.global_env, self.default_product) if add: name = 'milestone_from_' + threading.current_thread().name milestone = Milestone(env) milestone.name = name milestone.insert() else: # collect the names of milestones reported by Milestone and # directly from the db - as sets to ease comparison later results.append({ 'from_t': set([m.name for m in Milestone.select(env)]), 'from_db': set( [v[0] for v in self.env.db_query( "SELECT name FROM milestone")])}) def worker1(): """ check milestones in this thread twice either side of ceding control to worker2 """ task(False) e1.set() e2.wait() task(False) def worker2(): """ adds a milestone when worker1 allows us to then cede control back to worker1 """ e1.wait() task(True) e2.set() t1, t2 = [threading.Thread(target=f) for f in (worker1, worker2)] t1.start() t2.start() t1.join() t2.join() r = results[-1] # note we only care about the final result self.assertEqual(r['from_t'], r['from_db']) def test_update_milestone(self): self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')") milestone = Milestone(self.env, 'Test') t1 = datetime(2001, 01, 01, tzinfo=utc) t2 = datetime(2002, 02, 02, tzinfo=utc) milestone.due = t1 milestone.completed = t2 milestone.description = 'Foo bar' milestone.update() self.assertEqual( [('Test', to_utimestamp(t1), to_utimestamp(t2), 'Foo bar', self.default_product)], self.env.db_query("SELECT * FROM milestone WHERE name='Test'"))
class EnvironmentUpgradeTestCase(unittest.TestCase): def setUp(self, options=()): env_path = tempfile.mkdtemp(prefix='bh-product-tempenv-') self.env = Environment(env_path, create=True, options=options) DummyPlugin.version = 1 def tearDown(self): shutil.rmtree(self.env.path) def test_can_upgrade_environment_with_multi_product_disabled(self): self.env.upgrade() # Multiproduct was not enabled so multiproduct tables should not exist for table in BLOODHOUND_TABLES: with self.assertFailsWithMissingTable(): self.env.db_direct_query("SELECT * FROM %s" % table) for table in TABLES_WITH_PRODUCT_FIELD: with self.assertFailsWithMissingColumn(): self.env.db_direct_query("SELECT product FROM %s" % table) def test_upgrade_creates_multi_product_tables_and_adds_product_column(self): self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: for table in BLOODHOUND_TABLES: db("SELECT * FROM %s" % table) for table in TABLES_WITH_PRODUCT_FIELD: db("SELECT product FROM %s" % table) def test_upgrade_creates_default_product(self): self._enable_multiproduct() self.env.upgrade() products = Product.select(self.env) self.assertEqual(len(products), 1) def test_upgrade_moves_tickets_and_related_objects_to_default_prod(self): self._add_custom_field('custom_field') with self.env.db_direct_transaction as db: db("""INSERT INTO ticket (id) VALUES (1)""") db("""INSERT INTO attachment (type, id, filename) VALUES ('ticket', '1', '')""") db("""INSERT INTO ticket_custom (ticket, name, value) VALUES (1, 'custom_field', '42')""") db("""INSERT INTO ticket_change (ticket, time, field) VALUES (1, 42, 'summary')""") self._enable_multiproduct() self.env.upgrade() with self.product('@'): ticket = Ticket(self.env, 1) attachments = list(Attachment.select(self.env, ticket.resource.realm, ticket.resource.id)) self.assertEqual(len(attachments), 1) self.assertEqual(ticket['custom_field'], '42') changes = ticket.get_changelog() self.assertEqual(len(changes), 3) def test_upgrade_moves_custom_wikis_to_default_product(self): with self.env.db_direct_transaction as db: db("""INSERT INTO wiki (name, version) VALUES ('MyPage', 1)""") db("""INSERT INTO attachment (type, id, filename) VALUES ('wiki', 'MyPage', '')""") self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: self.assertEqual( len(db("""SELECT * FROM wiki WHERE product='@'""")), 1) self.assertEqual( len(db("""SELECT * FROM attachment WHERE product='@' AND type='wiki'""")), 1) def test_upgrade_moves_system_wikis_to_products(self): with self.env.db_direct_transaction as db: db("""INSERT INTO wiki (name, version) VALUES ('WikiStart', 1)""") db("""INSERT INTO attachment (type, id, filename) VALUES ('wiki', 'WikiStart', '')""") self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: self.assertEqual( len(db("""SELECT * FROM wiki WHERE product='@'""")), 1) self.assertEqual( len(db("""SELECT * FROM attachment WHERE product='@' AND type='wiki'""")), 1) self.assertEqual( len(db("""SELECT * FROM wiki WHERE product=''""")), 0) self.assertEqual( len(db("""SELECT * FROM attachment WHERE product='' AND type='wiki'""")), 0) def test_upgrade_copies_content_of_system_tables_to_all_products(self): mp = MultiProductSystem(self.env) with self.env.db_direct_transaction as db: mp._add_column_product_to_ticket(db) mp._create_multiproduct_tables(db) mp._update_db_version(db, 1) for i in range(1, 6): db("""INSERT INTO bloodhound_product (prefix, name) VALUES ('p%d', 'Product 1')""" % i) for table in ('component', 'milestone', 'enum', 'version', 'permission', 'report'): db("""DELETE FROM %s""" % table) db("""INSERT INTO component (name) VALUES ('foobar')""") db("""INSERT INTO milestone (name) VALUES ('foobar')""") db("""INSERT INTO version (name) VALUES ('foobar')""") db("""INSERT INTO enum (type, name) VALUES ('a', 'b')""") db("""INSERT INTO permission VALUES ('x', 'TICKET_VIEW')""") db("""INSERT INTO report (title) VALUES ('x')""") self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: for table in ('component', 'milestone', 'version', 'enum', 'report'): rows = db("SELECT * FROM %s" % table) self.assertEqual( len(rows), 6, "Wrong number of lines in %s (%d instead of %d)\n%s" % (table, len(rows), 6, rows)) for table in ('permission',): # Permissions also hold rows for global product. rows = db("SELECT * FROM %s WHERE username='******'" % table) self.assertEqual( len(rows), 7, "Wrong number of lines in %s (%d instead of %d)\n%s" % (table, len(rows), 7, rows)) def test_upgrading_database_moves_attachment_to_correct_product(self): ticket = self.insert_ticket('ticket') wiki = self.insert_wiki('MyWiki') attachment = self._create_file_with_content('Hello World!') self.add_attachment(ticket.resource, attachment) self.add_attachment(wiki.resource, attachment) self._enable_multiproduct() self.env.upgrade() with self.product('@'): attachments = list( Attachment.select(self.env, 'ticket', ticket.id)) attachments.extend( Attachment.select(self.env, 'wiki', wiki.name)) self.assertEqual(len(attachments), 2) for attachment in attachments: self.assertEqual(attachment.open().read(), 'Hello World!') def test_can_upgrade_database_with_ticket_attachment_with_text_ids(self): with self.env.db_direct_transaction as db: db("""INSERT INTO attachment (id, type, filename) VALUES ('abc', 'ticket', '')""") self._enable_multiproduct() self.env.upgrade() def test_can_upgrade_database_with_orphaned_attachments(self): with self.env.db_direct_transaction as db: db("""INSERT INTO attachment (id, type, filename) VALUES ('5', 'ticket', '')""") db("""INSERT INTO attachment (id, type, filename) VALUES ('MyWiki', 'wiki', '')""") self._enable_multiproduct() self.env.upgrade() def test_can_upgrade_multi_product_from_v1(self): mp = MultiProductSystem(self.env) with self.env.db_direct_transaction as db: mp._add_column_product_to_ticket(db) mp._create_multiproduct_tables(db) mp._update_db_version(db, 1) db("""INSERT INTO bloodhound_product (prefix, name) VALUES ('p1', 'Product 1')""") db("""INSERT INTO ticket (id, product) VALUES (1, 'Product 1')""") self._enable_multiproduct() self.env.upgrade() with self.product('p1'): Ticket(self.env, 1) def test_can_upgrade_multi_product_from_v2(self): mp = MultiProductSystem(self.env) with self.env.db_direct_transaction as db: mp._add_column_product_to_ticket(db) mp._create_multiproduct_tables(db) mp._replace_product_on_ticket_with_product_prefix(db) mp._update_db_version(db, 2) db("""INSERT INTO bloodhound_product (prefix, name) VALUES ('p1', 'Product 1')""") db("""INSERT INTO ticket (id, product) VALUES (1, 'p1')""") db("""INSERT INTO ticket (id) VALUES (2)""") self._enable_multiproduct() self.env.upgrade() with self.product('p1'): Ticket(self.env, 1) with self.product('@'): Ticket(self.env, 2) def test_upgrade_plugin(self): self._enable_component(DummyPlugin) self.env.upgrade() with self.env.db_direct_transaction as db: db("SELECT v1 FROM dummy_table") with self.assertFailsWithMissingColumn(): db("SELECT v2 FROM dummy_table") DummyPlugin.version = 2 self.env.upgrade() with self.env.db_direct_transaction as db: db("SELECT v2 FROM dummy_table") def test_upgrade_plugin_to_multiproduct(self): self._enable_multiproduct() self._enable_component(DummyPlugin) self.env.upgrade() with self.env.db_direct_transaction as db: db("SELECT * FROM dummy_table") db("""SELECT * FROM "@_dummy_table" """) def test_upgrade_existing_plugin_to_multiproduct(self): self._enable_component(DummyPlugin) self.env.upgrade() with self.env.db_direct_transaction as db: with self.assertFailsWithMissingTable(): db("""SELECT * FROM "@_dummy_table" """) self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: db("SELECT * FROM dummy_table") db("""SELECT * FROM "@_dummy_table" """) def test_upgrading_existing_plugin_leaves_data_in_global_env(self): DummyPlugin.version = 2 self._enable_component(DummyPlugin) self.env.upgrade() with self.env.db_direct_transaction as db: for i in range(5): db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i) self.assertEqual( len(db("SELECT * FROM dummy_table")), 5) self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: self.assertEqual( len(db('SELECT * FROM "dummy_table"')), 5) self.assertEqual( len(db('SELECT * FROM "@_dummy_table"')), 0) def test_creating_new_product_calls_environment_created(self): self._enable_component(DummyPlugin) self._enable_multiproduct() self.env.upgrade() prod = Product(self.env) prod.update_field_dict(dict(prefix='p1')) ProductEnvironment(self.env, prod, create=True) with self.env.db_direct_transaction as db: db('SELECT * FROM "p1_dummy_table"') def test_migrating_to_multiproduct_with_custom_default_prefix(self): ticket = self.insert_ticket('ticket') self.env.config.set('multiproduct', 'default_product_prefix', 'xxx') self._enable_multiproduct() self.env.upgrade() products = Product.select(self.env) self.assertEqual(len(products), 1) self.assertEqual(products[0].prefix, 'xxx') def test_migration_to_multiproduct_preserves_ticket_ids(self): for ticket_id in (1, 3, 5, 7): with self.env.db_transaction as db: cursor = db.cursor() cursor.execute("INSERT INTO ticket (id) VALUES (%i)" % ticket_id) db.update_sequence(cursor, 'ticket') self._enable_multiproduct() self.env.upgrade() for ticket_id in (1, 3, 5, 7): with self.product('@'): ticket = Ticket(self.env, ticket_id) self.assertEqual(ticket.id, ticket_id) def test_can_insert_tickets_after_upgrade(self): t1 = Ticket(self.env) t1.summary = "test" t1.insert() self.assertEqual(t1.id, 1) self._enable_multiproduct() self.env.upgrade() with self.product('@'): ticket = Ticket(self.env) ticket.summary = 'test' ticket.insert() self.assertEqual(ticket.id, 2) def test_can_insert_tickets_with_same_id_to_different_products(self): self._enable_multiproduct() self.env.upgrade() self.env.db_transaction("INSERT INTO ticket (id, summary)" " VALUES (1, 'first product')") t1 = Ticket(self.env, 1) with self.product('@'): self.env.db_transaction("INSERT INTO ticket (id, summary)" " VALUES (1, 'second product')") t2 = Ticket(self.env, 1) self.assertEqual(t1.id, t2.id) self.assertNotEqual(t1['summary'], t2['summary']) def test_batch_ticket_insert_after_upgrade(self): self._enable_multiproduct() self.env.upgrade() with self.env.db_direct_transaction as db: db("""CREATE TABLE "@_tmp" (summary text, product text)""") for summary in "abcdef": db("""INSERT INTO "@_tmp" VALUES ('%s', '@')""" % (summary,)) with self.product('@'): with self.env.db_transaction as db: db("""INSERT INTO ticket (summary) SELECT summary FROM tmp""") def _enable_multiproduct(self): self._update_config('components', 'multiproduct.*', 'enabled') def _add_custom_field(self, field_name): self._update_config('ticket-custom', field_name, 'text') def _enable_component(self, cls): self._update_config( 'components', '%s.%s' % (cls.__module__, cls.__name__), 'enabled' ) def _update_config(self, section, key, value): self.env.config.set(section, key, value) self.env.config.save() self.env = Environment(self.env.path) def _create_file_with_content(self, content): filename = str(uuid.uuid4())[:6] path = os.path.join(self.env.path, filename) with open(path, 'wb') as f: f.write(content) return path @contextmanager def assertFailsWithMissingTable(self): with self.assertRaises(self.env.db_exc.OperationalError) as cm: yield self.assertIn('no such table', str(cm.exception)) @contextmanager def assertFailsWithMissingColumn(self): with self.assertRaises(self.env.db_exc.OperationalError) as cm: yield self.assertIn('no such column', str(cm.exception)) def create_ticket(self, summary, **kw): ticket = Ticket(self.env) ticket["summary"] = summary for k, v in kw.items(): ticket[k] = v return ticket def insert_ticket(self, summary, **kw): """Helper for inserting a ticket into the database""" ticket = self.create_ticket(summary, **kw) ticket.insert() return ticket def create_wiki(self, name, text, **kw): page = WikiPage(self.env, name) page.text = text for k, v in kw.items(): page[k] = v return page def insert_wiki(self, name, text = None, **kw): text = text or "Dummy text" page = self.create_wiki(name, text, **kw) page.save("dummy author", "dummy comment", "::1") return page def add_attachment(self, resource, path): resource = '%s:%s' % (resource.realm, resource.id) AttachmentAdmin(self.env)._do_add(resource, path) @contextmanager def product(self, prefix): old_env = self.env self.env = ProductEnvironment(self.env, prefix) yield self.env = old_env
def expand_macro(self, formatter, name, content): req = formatter.req query_string, kwargs, format = self.parse_args(content) if query_string: query_string += '&' query_string += '&'.join('%s=%s' % item for item in kwargs.iteritems()) env = ProductEnvironment.lookup_global_env(self.env) query = ProductQuery.from_string(env, query_string) if format == 'count': cnt = query.count(req) return tag.span(cnt, title='%d tickets for which %s' % (cnt, query_string), class_='query_count') tickets = query.execute(req) if format == 'table': data = query.template_data(formatter.context, tickets, req=formatter.context.req) add_stylesheet(req, 'common/css/report.css') return Chrome(env).render_template( req, 'query_results.html', data, None, fragment=True) if format == 'progress': from trac.ticket.roadmap import (RoadmapModule, apply_ticket_permissions, get_ticket_stats, grouped_stats_data) add_stylesheet(req, 'common/css/roadmap.css') def query_href(extra_args, group_value = None): q = ProductQuery.from_string(env, query_string) if q.group: extra_args[q.group] = group_value q.group = None for constraint in q.constraints: constraint.update(extra_args) if not q.constraints: q.constraints.append(extra_args) return q.get_href(formatter.context) chrome = Chrome(env) tickets = apply_ticket_permissions(env, req, tickets) stats_provider = RoadmapModule(env).stats_provider by = query.group if not by: stat = get_ticket_stats(stats_provider, tickets) data = { 'stats': stat, 'stats_href': query_href(stat.qry_args), 'interval_hrefs': [query_href(interval['qry_args']) for interval in stat.intervals], 'legend': True, } return tag.div( chrome.render_template(req, 'progress_bar.html', data, None, fragment=True), class_='trac-progress') def per_group_stats_data(gstat, group_name): return { 'stats': gstat, 'stats_href': query_href(gstat.qry_args, group_name), 'interval_hrefs': [query_href(interval['qry_args'], group_name) for interval in gstat.intervals], 'percent': '%d / %d' % (gstat.done_count, gstat.count), 'legend': False, } groups = grouped_stats_data(env, stats_provider, tickets, by, per_group_stats_data) data = { 'groups': groups, 'grouped_by': by, 'summary': _("Ticket completion status for each %(group)s", group=by), } return tag.div( chrome.render_template(req, 'progress_bar_grouped.html', data, None, fragment=True), class_='trac-groupprogress') # Formats above had their own permission checks, here we need to # do it explicitly: tickets = [t for t in tickets if 'TICKET_VIEW' in req.perm('ticket', t['id'])] if not tickets: return tag.span(_("No results"), class_='query_no_results') # Cache resolved href targets hrefcache = {} def ticket_anchor(ticket): try: pvalue = ticket.get('product') or GLOBAL_PRODUCT envhref = hrefcache[pvalue] except KeyError: try: env = lookup_product_env(self.env, prefix= pvalue, name=pvalue) except LookupError: return tag.a('#%s' % ticket['id'], class_='missing product') hrefcache[pvalue] = envhref = resolve_product_href( to_env=env, at_env=self.env) return tag.a('#%s' % ticket['id'], class_=ticket['status'], href=envhref.ticket(int(ticket['id'])), title=shorten_line(ticket['summary'])) def ticket_groups(): groups = [] for v, g in groupby(tickets, lambda t: t[query.group]): q = ProductQuery.from_string(env, query_string) # produce the hint for the group q.group = q.groupdesc = None order = q.order q.order = None title = _("%(groupvalue)s %(groupname)s tickets matching " "%(query)s", groupvalue=v, groupname=query.group, query=q.to_string()) # produce the href for the query corresponding to the group for constraint in q.constraints: constraint[str(query.group)] = v q.order = order href = q.get_href(formatter.context) groups.append((v, [t for t in g], href, title)) return groups if format == 'compact': if query.group: groups = [(v, ' ', tag.a('#%s' % u',\u200b'.join(str(t['id']) for t in g), href=href, class_='query', title=title)) for v, g, href, title in ticket_groups()] return tag(groups[0], [(', ', g) for g in groups[1:]]) else: alist = [ticket_anchor(ticket) for ticket in tickets] return tag.span(alist[0], *[(', ', a) for a in alist[1:]]) else: if query.group: return tag.div( [(tag.p(tag_('%(groupvalue)s %(groupname)s tickets:', groupvalue=tag.a(v, href=href, class_='query', title=title), groupname=query.group)), tag.dl([(tag.dt(ticket_anchor(t)), tag.dd(t['summary'])) for t in g], class_='wiki compact')) for v, g, href, title in ticket_groups()]) else: return tag.div(tag.dl([(tag.dt(ticket_anchor(ticket)), tag.dd(ticket['summary'])) for ticket in tickets], class_='wiki compact'))
class ProductModuleTestCase(RequestHandlerTestCase): def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) self.global_env.enable_component(TestRequestSpy) self.env.enable_component(TestRequestSpy) TestRequestSpy(self.global_env).testMatch = self._assert_product_match PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_CREATE') PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_VIEW') PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_MODIFY') def tearDown(self): self.global_env.reset_db() self.env = self.global_env = None expectedPrefix = None expectedPathInfo = None def _assert_product_match(self, req, handler): self.assertIs(ProductModule(self.global_env), handler) self.assertEqual(self.expectedPrefix, req.args['productid'], "Unexpected product prefix") self.assertEqual(self.expectedPathInfo, req.args['pathinfo'], "Unexpected sub path") def test_product_list(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['PATH_INFO'] = '/products' mps = MultiProductSystem(self.global_env) def assert_product_list(req, template, data, content_type): self.assertEquals('product_list.html', template) self.assertIs(None, content_type) self.assertEquals([mps.default_product_prefix, self.default_product], [p.prefix for p in data.get('products')]) self.assertTrue('context' in data) ctx = data['context'] self.assertEquals('product', ctx.resource.realm) self.assertEquals(None, ctx.resource.id) spy.testProcessing = assert_product_list with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) def test_product_new(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['PATH_INFO'] = '/products' req.environ['QUERY_STRING'] = 'action=new' def assert_product_new(req, template, data, content_type): self.assertEquals('product_edit.html', template) self.assertIs(None, content_type) self.assertFalse('products' in data) self.assertTrue('context' in data) ctx = data['context'] self.assertEquals('product', ctx.resource.realm) self.assertEquals(None, ctx.resource.id) spy.testProcessing = assert_product_new with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) def test_product_view(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) def assert_product_view(req, template, data, content_type): self.assertEquals('product_view.html', template) self.assertIs(None, content_type) self.assertFalse('products' in data) self.assertTrue('context' in data) ctx = data['context'] self.assertEquals('product', ctx.resource.realm) self.assertEquals(real_prefix, ctx.resource.id) self.assertTrue('product' in data) self.assertEquals(real_prefix, data['product'].prefix) spy.testProcessing = assert_product_view # Existing product req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,) real_prefix = self.default_product self.expectedPrefix = self.default_product self.expectedPathInfo = '' with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) def test_missing_product(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) mps = MultiProductSystem(self.global_env) def assert_product_list(req, template, data, content_type): self.assertEquals('product_list.html', template) self.assertIs(None, content_type) self.assertEquals([mps.default_product_prefix, self.default_product], [p.prefix for p in data.get('products')]) self.assertTrue('context' in data) ctx = data['context'] self.assertEquals('product', ctx.resource.realm) self.assertEquals(None, ctx.resource.id) spy.testProcessing = assert_product_list # Missing product req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['PATH_INFO'] = '/products/missing' self.expectedPrefix = 'missing' self.expectedPathInfo = '' with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) self.assertEqual(1, len(req.chrome['warnings'])) self.assertEqual('Product missing not found', req.chrome['warnings'][0].unescape()) def test_product_edit(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) # HTTP GET req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,) req.environ['QUERY_STRING'] = 'action=edit' real_prefix = self.default_product def assert_product_edit(req, template, data, content_type): self.assertEquals('product_edit.html', template) self.assertIs(None, content_type) self.assertFalse('products' in data) self.assertTrue('context' in data) ctx = data['context'] self.assertEquals('product', ctx.resource.realm) self.assertEquals(real_prefix, ctx.resource.id) self.assertTrue('product' in data) self.assertEquals(real_prefix, data['product'].prefix) spy.testProcessing = assert_product_edit self.expectedPrefix = self.default_product self.expectedPathInfo = '' with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) # HTTP POST req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['REQUEST_METHOD'] = 'POST' req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,) req.args = dict(action='edit', description='New description', prefix=self.default_product, name=self.env.product.name) spy.testProcessing = assert_product_edit self.expectedPrefix = self.default_product self.expectedPathInfo = '' self.record_response = True with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) try: product = Product(self.global_env, {'prefix' : self.env.product.prefix}) except ResourceNotFound: self.fail('Default test product deleted ?') else: self.assertEquals('New description', product.description) product_url = Href(req.base_path).products(self.default_product) self.assertRedirect(req, product_url) def test_product_delete(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) req = self._get_request_obj(self.global_env) req.authname = 'testuser' req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,) req.environ['QUERY_STRING'] = 'action=delete' self.expectedPrefix = self.default_product self.expectedPathInfo = '' spy.testProcessing = lambda *args, **kwargs: None with self.assertRaises(HTTPInternalError) as test_cm: self._dispatch(req, self.global_env) self.assertEqual('500 Trac Error (Product removal is not allowed!)', unicode(test_cm.exception))
def setUp(self, options=()): self.env_path = tempfile.mkdtemp('multiproduct-tempenv') self.env = Environment(self.env_path, create=True, options=options) DummyPlugin.version = 1
class ProductModuleTestCase(RequestHandlerTestCase): def setUp(self): self._mp_setup() self.global_env = self.env self.env = ProductEnvironment(self.global_env, self.default_product) self.global_env.enable_component(TestRequestSpy) self.env.enable_component(TestRequestSpy) TestRequestSpy(self.global_env).testMatch = self._assert_product_match PermissionSystem(self.global_env).grant_permission("testuser", "PRODUCT_CREATE") PermissionSystem(self.global_env).grant_permission("testuser", "PRODUCT_VIEW") PermissionSystem(self.global_env).grant_permission("testuser", "PRODUCT_MODIFY") def tearDown(self): self.global_env.reset_db() self.env = self.global_env = None expectedPrefix = None expectedPathInfo = None def _assert_product_match(self, req, handler): self.assertIs(ProductModule(self.global_env), handler) self.assertEqual(self.expectedPrefix, req.args["productid"], "Unexpected product prefix") self.assertEqual(self.expectedPathInfo, req.args["pathinfo"], "Unexpected sub path") def test_product_list(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["PATH_INFO"] = "/products" mps = MultiProductSystem(self.global_env) def assert_product_list(req, template, data, content_type): self.assertEquals("product_list.html", template) self.assertIs(None, content_type) self.assertEquals( [mps.default_product_prefix, self.default_product], [p.prefix for p in data.get("products")] ) self.assertTrue("context" in data) ctx = data["context"] self.assertEquals("product", ctx.resource.realm) self.assertEquals(None, ctx.resource.id) spy.testProcessing = assert_product_list with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) def test_product_new(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["PATH_INFO"] = "/products" req.environ["QUERY_STRING"] = "action=new" def assert_product_new(req, template, data, content_type): self.assertEquals("product_edit.html", template) self.assertIs(None, content_type) self.assertFalse("products" in data) self.assertTrue("context" in data) ctx = data["context"] self.assertEquals("product", ctx.resource.realm) self.assertEquals(None, ctx.resource.id) spy.testProcessing = assert_product_new with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) def test_product_view(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) def assert_product_view(req, template, data, content_type): self.assertEquals("product_view.html", template) self.assertIs(None, content_type) self.assertFalse("products" in data) self.assertTrue("context" in data) ctx = data["context"] self.assertEquals("product", ctx.resource.realm) self.assertEquals(real_prefix, ctx.resource.id) self.assertTrue("product" in data) self.assertEquals(real_prefix, data["product"].prefix) spy.testProcessing = assert_product_view # Existing product req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["PATH_INFO"] = "/products/%s" % (self.default_product,) real_prefix = self.default_product self.expectedPrefix = self.default_product self.expectedPathInfo = "" with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) def test_missing_product(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) mps = MultiProductSystem(self.global_env) def assert_product_list(req, template, data, content_type): self.assertEquals("product_list.html", template) self.assertIs(None, content_type) self.assertEquals( [mps.default_product_prefix, self.default_product], [p.prefix for p in data.get("products")] ) self.assertTrue("context" in data) ctx = data["context"] self.assertEquals("product", ctx.resource.realm) self.assertEquals(None, ctx.resource.id) spy.testProcessing = assert_product_list # Missing product req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["PATH_INFO"] = "/products/missing" self.expectedPrefix = "missing" self.expectedPathInfo = "" with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) self.assertEqual(1, len(req.chrome["warnings"])) self.assertEqual("Product missing not found", req.chrome["warnings"][0].unescape()) def test_product_edit(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) # HTTP GET req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["PATH_INFO"] = "/products/%s" % (self.default_product,) req.environ["QUERY_STRING"] = "action=edit" real_prefix = self.default_product def assert_product_edit(req, template, data, content_type): self.assertEquals("product_edit.html", template) self.assertIs(None, content_type) self.assertFalse("products" in data) self.assertTrue("context" in data) ctx = data["context"] self.assertEquals("product", ctx.resource.realm) self.assertEquals(real_prefix, ctx.resource.id) self.assertTrue("product" in data) self.assertEquals(real_prefix, data["product"].prefix) spy.testProcessing = assert_product_edit self.expectedPrefix = self.default_product self.expectedPathInfo = "" with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) # HTTP POST req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["REQUEST_METHOD"] = "POST" req.environ["PATH_INFO"] = "/products/%s" % (self.default_product,) req.args = dict( action="edit", description="New description", prefix=self.default_product, name=self.env.product.name ) spy.testProcessing = assert_product_edit self.expectedPrefix = self.default_product self.expectedPathInfo = "" self.record_response = True with self.assertRaises(RequestDone): self._dispatch(req, self.global_env) try: product = Product(self.global_env, {"prefix": self.env.product.prefix}) except ResourceNotFound: self.fail("Default test product deleted ?") else: self.assertEquals("New description", product.description) product_url = Href(req.base_path).products(self.default_product) self.assertRedirect(req, product_url) def test_product_delete(self): spy = self.global_env[TestRequestSpy] self.assertIsNot(None, spy) req = self._get_request_obj(self.global_env) req.authname = "testuser" req.environ["PATH_INFO"] = "/products/%s" % (self.default_product,) req.environ["QUERY_STRING"] = "action=delete" self.expectedPrefix = self.default_product self.expectedPathInfo = "" spy.testProcessing = lambda *args, **kwargs: None with self.assertRaises(HTTPInternalError) as test_cm: self._dispatch(req, self.global_env) self.assertEqual("500 Trac Error (Product removal is not allowed!)", unicode(test_cm.exception))
def _update_config(self, section, key, value): self.env.config.set(section, key, value) self.env.config.save() self.env = Environment(self.env.path)
def setUp(self, options=()): env_path = tempfile.mkdtemp(prefix='bh-product-tempenv-') self.env = Environment(env_path, create=True, options=options) DummyPlugin.version = 1
def admin_url(prefix): env = ProductEnvironment.lookup_env(self.env, prefix) href = ProductEnvironment.resolve_href(env, self.env) return href.admin()
def product(self, prefix): old_env = self.env self.env = ProductEnvironment(self.env, prefix) yield self.env = old_env