Ejemplo n.º 1
0
def _MakeTableData(issues, starred_iid_set, lower_columns, lower_group_by,
                   users_by_id, cell_factories, related_issues, config,
                   context_for_all_issues, hotlist_id, sort_spec):
    """Returns data from MakeTableData after adding additional information."""
    table_data = table_view_helpers.MakeTableData(
        issues, starred_iid_set, lower_columns, lower_group_by, users_by_id,
        cell_factories, lambda issue: issue.issue_id, related_issues, config,
        context_for_all_issues)

    for row, art in zip(table_data, issues):
        row.issue_id = art.issue_id
        row.local_id = art.local_id
        row.project_name = art.project_name
        row.project_url = framework_helpers.FormatURL(
            None, '/p/%s' % row.project_name)
        row.issue_ref = '%s:%d' % (art.project_name, art.local_id)
        row.issue_clean_url = tracker_helpers.FormatRelativeIssueURL(
            art.project_name, urls.ISSUE_DETAIL, id=art.local_id)
        row.issue_ctx_url = tracker_helpers.FormatRelativeIssueURL(
            art.project_name,
            urls.ISSUE_DETAIL,
            id=art.local_id,
            sort=sort_spec,
            hotlist_id=hotlist_id)

    return table_data
 def testFormatURLWithStrangeParams(self):
   mr = testing_helpers.MakeMonorailRequest(path='/foo?start=0')
   url = framework_helpers.FormatURL(
       mr, '/foo', r=0, path='/foo/bar', sketchy='/foo/ bar baz ')
   self.assertEqual(
       '/foo?start=0&path=/foo/bar&r=0&sketchy=/foo/%20bar%20baz%20',
       url)
Ejemplo n.º 3
0
    def get(self, project_name=None, viewed_username=None, hotlist_id=None):
        with work_env.WorkEnv(self.mr, self.services) as we:
            hotlist_id = self.mr.GetIntParam('hotlist_id')
            current_issue = we.GetIssueByLocalID(self.mr.project_id,
                                                 self.mr.local_id,
                                                 use_cache=False)
            hotlist = None
            if hotlist_id:
                try:
                    hotlist = self.services.features.GetHotlist(
                        self.mr.cnxn, hotlist_id)
                except features_svc.NoSuchHotlistException:
                    pass

            try:
                adj_issue = GetAdjacentIssue(we,
                                             current_issue,
                                             hotlist=hotlist,
                                             next_issue=self.next_handler)
                path = '/p/%s%s' % (adj_issue.project_name, urls.ISSUE_DETAIL)
                url = framework_helpers.FormatURL(
                    [(name, self.mr.GetParam(name))
                     for name in framework_helpers.RECOGNIZED_PARAMS],
                    path,
                    id=adj_issue.local_id)
            except exceptions.NoSuchIssueException:
                config = we.GetProjectConfig(self.mr.project_id)
                url = _ComputeBackToListURL(self.mr, current_issue, config,
                                            hotlist, self.services)
            self.redirect(url)
Ejemplo n.º 4
0
def _StartBackendSearchCall(mr,
                            query_project_names,
                            shard_key,
                            invalidation_timestep,
                            deadline=None,
                            failfast=True):
    """Ask a backend to query one shard of the database."""
    shard_id, subquery = shard_key
    backend_host = modules.get_hostname(module='besearch')
    url = 'http://%s%s' % (backend_host,
                           framework_helpers.FormatURL(
                               mr,
                               urls.BACKEND_SEARCH,
                               projects=','.join(query_project_names),
                               q=subquery,
                               start=0,
                               num=mr.start + mr.num,
                               logged_in_user_id=mr.auth.user_id or 0,
                               me_user_id=mr.me_user_id,
                               shard_id=shard_id,
                               invalidation_timestep=invalidation_timestep))
    logging.info('\n\nCalling backend: %s', url)
    rpc = urlfetch.create_rpc(deadline=deadline or settings.backend_deadline)
    headers = _MakeBackendRequestHeaders(failfast)
    # follow_redirects=False is needed to avoid a login screen on googleplex.
    urlfetch.make_fetch_call(rpc, url, follow_redirects=False, headers=headers)
    return rpc
 def testFormatURL(self):
     mr = testing_helpers.MakeMonorailRequest()
     path = '/dude/wheres/my/car'
     recognized_params = [(name, mr.GetParam(name))
                          for name in framework_helpers.RECOGNIZED_PARAMS]
     url = framework_helpers.FormatURL(recognized_params, path)
     self.assertEqual(path, url)
Ejemplo n.º 6
0
  def GatherPageData(self, mr):
    """Build up a dictionary of data values to use when rendering the page.

    Args:
      mr: commonly usef info parsed from the request.

    Returns:
      Dict of values used by EZT for rendering the page.
    """
    with self.profiler.Phase('getting hotlist'):
      if mr.hotlist_id is None:
        self.abort(404, 'no hotlist specified')
    if mr.auth.user_id:
      self.services.user.AddVisitedHotlist(
          mr.cnxn, mr.auth.user_id, mr.hotlist_id)

    if mr.mode == 'grid':
      page_data = self.GetGridViewData(mr)
    else:
      page_data = self.GetTableViewData(mr)

    with self.profiler.Phase('making page perms'):
      owner_permissions = permissions.CanAdministerHotlist(
          mr.auth.effective_ids, mr.hotlist)
      editor_permissions = permissions.CanEditHotlist(
          mr.auth.effective_ids, mr.hotlist)
      # TODO(jojwang): each issue should have an individual
      # SetStar status based on its project to indicate whether or not
      # the star icon should be shown to the user.
      page_perms = template_helpers.EZTItem(
          EditIssue=None, SetStar=mr.auth.user_id)

    allow_rerank = (not mr.group_by_spec and mr.sort_spec.startswith(
        'rank') and (owner_permissions or editor_permissions))

    user_hotlists = self.services.features.GetHotlistsByUserID(
        mr.cnxn, mr.auth.user_id)
    try:
      user_hotlists.remove(self.services.features.GetHotlist(
          mr.cnxn, mr.hotlist_id))
    except ValueError:
      pass
    # Note: The HotlistView is created and returned in servlet.py
    page_data.update({'owner_permissions': ezt.boolean(owner_permissions),
                      'editor_permissions': ezt.boolean(editor_permissions),
                      'issue_tab_mode': 'issueList',
                      'grid_mode': ezt.boolean(mr.mode == 'grid'),
                      'set_star_token': '', # needed for shared ezt templates.
                      'page_perms': page_perms,
                      'colspec': mr.col_spec,
                      'allow_rerank': ezt.boolean(allow_rerank),
                      'csv_link': framework_helpers.FormatURL(
                          mr, '%d/csv' % mr.hotlist_id, num=100),
                      'is_hotlist': ezt.boolean(True),
                      'col_spec': mr.col_spec.lower(),
                      'user_hotlists': user_hotlists,
                      'viewing_user_page': ezt.boolean(True),
                      })
    return page_data
Ejemplo n.º 7
0
  def GetTableViewData(
      self, mr, results, config, users_by_id, starred_iid_set, related_issues,
      viewable_iids_set):
    """EZT template values to render a Table View of issues.

    Args:
      mr: commonly used info parsed from the request.
      results: list of Issue PBs for the search results to be displayed.
      config: The ProjectIssueConfig PB for the current project.
      users_by_id: A dictionary {user_id: UserView} for all the users
      involved in results.
      starred_iid_set: Set of issues that the user has starred.
      related_issues: dict {issue_id: issue} of pre-fetched related issues.
      viewable_iids_set: set of issue ids that are viewable by the user.

    Returns:
      Dictionary of page data for rendering of the Table View.
    """
    # We need ordered_columns because EZT loops have no loop-counter available.
    # And, we use column number in the Javascript to hide/show columns.
    columns = mr.col_spec.split()
    ordered_columns = [template_helpers.EZTItem(col_index=i, name=col)
                       for i, col in enumerate(columns)]
    unshown_columns = table_view_helpers.ComputeUnshownColumns(
        results, columns, config, tracker_constants.OTHER_BUILT_IN_COLS)

    lower_columns = mr.col_spec.lower().split()
    lower_group_by = mr.group_by_spec.lower().split()
    table_data = _MakeTableData(
        results, starred_iid_set, lower_columns, lower_group_by,
        users_by_id, self.GetCellFactories(), related_issues,
        viewable_iids_set, config)

    # Used to offer easy filtering of each unique value in each column.
    column_values = table_view_helpers.ExtractUniqueValues(
        lower_columns, results, users_by_id, config, related_issues)

    table_view_data = {
        'table_data': table_data,
        'column_values': column_values,
        # Put ordered_columns inside a list of exactly 1 panel so that
        # it can work the same as the dashboard initial panel list headers.
        'panels': [template_helpers.EZTItem(ordered_columns=ordered_columns)],
        'unshown_columns': unshown_columns,
        'cursor': mr.cursor or mr.preview,
        'preview': mr.preview,
        'default_colspec': tracker_constants.DEFAULT_COL_SPEC,
        'default_results_per_page': tracker_constants.DEFAULT_RESULTS_PER_PAGE,
        'csv_link': framework_helpers.FormatURL(
            [(name, mr.GetParam(name)) for name in
             framework_helpers.RECOGNIZED_PARAMS],
            'csv', num=settings.max_artifact_search_results_per_page),
        'preview_on_hover': ezt.boolean(
            _ShouldPreviewOnHover(mr.auth.user_pb)),
        }
    return table_view_data
 def testFormatURLWithStrangeParams(self):
     mr = testing_helpers.MakeMonorailRequest(path='/foo?start=0')
     recognized_params = [(name, mr.GetParam(name))
                          for name in framework_helpers.RECOGNIZED_PARAMS]
     url = framework_helpers.FormatURL(recognized_params,
                                       '/foo',
                                       r=0,
                                       path='/foo/bar',
                                       sketchy='/foo/ bar baz ')
     self.assertEqual(
         '/foo?start=0&path=/foo/bar&r=0&sketchy=/foo/%20bar%20baz%20', url)
 def testFormatURLWithRecognizedParams(self):
   params = {}
   query = []
   for name in framework_helpers.RECOGNIZED_PARAMS:
     params[name] = name
     query.append('%s=%s' % (name, 123))
   path = '/dude/wheres/my/car'
   expected = '%s?%s' % (path, '&'.join(query))
   mr = testing_helpers.MakeMonorailRequest(path=expected)
   url = framework_helpers.FormatURL(mr, path)  # No added params.
   self.assertEqual(expected, url)
Ejemplo n.º 10
0
def FormatIssueURL(issue_ref_tuple, default_project_name=None):
    """Format an issue url from an issue ref."""
    if issue_ref_tuple is None:
        return ''
    project_name, local_id = issue_ref_tuple
    project_name = project_name or default_project_name
    url = framework_helpers.FormatURL(None,
                                      '/p/%s%s' %
                                      (project_name, urls.ISSUE_DETAIL),
                                      id=local_id)
    return url
Ejemplo n.º 11
0
def FormatRelativeIssueURL(project_name, path, **kwargs):
    """Format a URL to get to an issue in the named project.

  Args:
    project_name: string name of the project containing the issue.
    path: string servlet path, e.g., from framework/urls.py.
    **kwargs: additional query-string parameters to include in the URL.

  Returns:
    A URL string.
  """
    return framework_helpers.FormatURL(None, '/p/%s%s' % (project_name, path),
                                       **kwargs)
 def testFormatURLWithKeywordArgs(self):
   params = {}
   query_pairs = []
   for name in framework_helpers.RECOGNIZED_PARAMS:
     params[name] = name
     if name is not 'can' and name is not 'start':
       query_pairs.append('%s=%s' % (name, 123))
   path = '/dude/wheres/my/car'
   mr = testing_helpers.MakeMonorailRequest(
       path='%s?%s' % (path, '&'.join(query_pairs)))
   query_pairs.append('can=yep')
   query_pairs.append('start=486')
   query_string = '&'.join(query_pairs)
   expected = '%s?%s' % (path, query_string)
   url = framework_helpers.FormatURL(mr, path, can='yep', start=486)
   self.assertEqual(expected, url)
Ejemplo n.º 13
0
def _StartBackendNonviewableCall(project_id,
                                 logged_in_user_id,
                                 shard_id,
                                 invalidation_timestep,
                                 deadline=None,
                                 failfast=True):
    """Ask a backend to query one shard of the database."""
    backend_host = modules.get_hostname(module='besearch')
    url = 'http://%s%s' % (backend_host,
                           framework_helpers.FormatURL(
                               None,
                               urls.BACKEND_NONVIEWABLE,
                               project_id=project_id or '',
                               logged_in_user_id=logged_in_user_id or '',
                               shard_id=shard_id,
                               invalidation_timestep=invalidation_timestep))
    logging.info('Calling backend nonviewable: %s', url)
    rpc = urlfetch.create_rpc(deadline=deadline or settings.backend_deadline)
    headers = _MakeBackendRequestHeaders(failfast)
    # follow_redirects=False is needed to avoid a login screen on googleplex.
    urlfetch.make_fetch_call(rpc, url, follow_redirects=False, headers=headers)
    return rpc
 def testFormatURLWithKeywordArgsAndID(self):
     params = {}
     query_pairs = []
     query_pairs.append('id=200')  # id should be the first parameter.
     for name in framework_helpers.RECOGNIZED_PARAMS:
         params[name] = name
         if name != 'can' and name != 'start':
             query_pairs.append('%s=%s' % (name, 123))
     path = '/dude/wheres/my/car'
     mr = testing_helpers.MakeMonorailRequest(path='%s?%s' %
                                              (path, '&'.join(query_pairs)))
     query_pairs.append('can=yep')
     query_pairs.append('start=486')
     query_string = '&'.join(query_pairs)
     expected = '%s?%s' % (path, query_string)
     recognized_params = [(name, mr.GetParam(name))
                          for name in framework_helpers.RECOGNIZED_PARAMS]
     url = framework_helpers.FormatURL(recognized_params,
                                       path,
                                       can='yep',
                                       start=486,
                                       id=200)
     self.assertEqual(expected, url)
Ejemplo n.º 15
0
    def __init__(self,
                 total_count,
                 items_per_page,
                 start,
                 list_page_url=None,
                 count_up=True,
                 start_param_name='start',
                 num_param_name='num',
                 max_num=None,
                 url_params=None,
                 project_name=None):
        """Given 'num' and 'start' params, determine Prev and Next links.

    Args:
      total_count: total number of artifacts that satisfy the query.
      items_per_page: number of items to display on each page, e.g., 25.
      start: the start index of the pagination page.
      list_page_url: URL of the web application page that is displaying
        the list of artifacts.  Used to build the Prev and Next URLs.
        If None, no URLs will be built.
      count_up: if False, count down from total_count.
      start_param_name: query string parameter name for the start value
        of the pagination page.
      num_param: query string parameter name for the number of items
        to show on a pagination page.
      max_num: optional limit on the value of the num param.  If not given,
        settings.max_artifact_search_results_per_page is used.
      url_params: list of (param_name, param_value) we want to keep
        in any new urls.
      project_name: the name of the project we are operating in.
    """
        self.total_count = total_count
        self.prev_url = ''
        self.reload_url = ''
        self.next_url = ''

        if max_num is None:
            max_num = settings.max_artifact_search_results_per_page

        self.num = items_per_page
        self.num = min(self.num, max_num)

        if count_up:
            self.start = start or 0
            self.last = min(self.total_count, self.start + self.num)
            prev_start = max(0, self.start - self.num)
            next_start = self.start + self.num
        else:
            self.start = start or self.total_count
            self.last = max(0, self.start - self.num)
            prev_start = min(self.total_count, self.start + self.num)
            next_start = self.start - self.num

        if list_page_url:
            if project_name:
                list_servlet_rel_url = '/p/%s%s' % (project_name,
                                                    list_page_url)
            else:
                list_servlet_rel_url = list_page_url

            self.reload_url = framework_helpers.FormatURL(
                url_params, list_servlet_rel_url, **{
                    start_param_name: self.start,
                    num_param_name: self.num
                })

            if prev_start != self.start:
                self.prev_url = framework_helpers.FormatURL(
                    url_params, list_servlet_rel_url, **{
                        start_param_name: prev_start,
                        num_param_name: self.num
                    })
            if ((count_up and next_start < self.total_count)
                    or (not count_up and next_start >= 1)):
                self.next_url = framework_helpers.FormatURL(
                    url_params, list_servlet_rel_url, **{
                        start_param_name: next_start,
                        num_param_name: self.num
                    })

        self.visible = ezt.boolean(self.last != self.start)

        # Adjust indices to one-based values for display to users.
        if count_up:
            self.start += 1
        else:
            self.last += 1
Ejemplo n.º 16
0
def GatherUpdatesData(services,
                      mr,
                      project_ids=None,
                      user_ids=None,
                      ending=None,
                      updates_page_url=None,
                      autolink=None,
                      highlight=None):
    """Gathers and returns updates data.

  Args:
    services: Connections to backend services.
    mr: HTTP request info, used by the artifact autolink.
    project_ids: List of project IDs we want updates for.
    user_ids: List of user IDs we want updates for.
    ending: Ending type for activity titles, 'in_project' or 'by_user'.
    updates_page_url: The URL that will be used to create pagination links from.
    autolink: Autolink instance.
    highlight: What to highlight in the middle column on user updates pages
        i.e. 'project', 'user', or None.
  """
    # num should be non-negative number
    num = mr.GetPositiveIntParam('num', UPDATES_PER_PAGE)
    num = min(num, MAX_UPDATES_PER_PAGE)

    updates_data = {
        'no_stars': None,
        'no_activities': None,
        'pagination': None,
        'updates_data': None,
        'ending_type': ending,
    }

    if not user_ids and not project_ids:
        updates_data['no_stars'] = ezt.boolean(True)
        return updates_data

    ascending = bool(mr.after)
    with mr.profiler.Phase('get activities'):
        comments = services.issue.GetIssueActivity(mr.cnxn,
                                                   num=num,
                                                   before=mr.before,
                                                   after=mr.after,
                                                   project_ids=project_ids,
                                                   user_ids=user_ids,
                                                   ascending=ascending)
        # Filter the comments based on permission to view the issue.
        # TODO(jrobbins): push permission checking in the query so that
        # pagination pages never become underfilled, or use backends to shard.
        # TODO(jrobbins): come back to this when I implement private comments.
        # TODO(jrobbins): it would be better if we could just get the dict directly.
        prefetched_issues_list = services.issue.GetIssues(
            mr.cnxn, {c.issue_id
                      for c in comments})
        prefetched_issues = {
            issue.issue_id: issue
            for issue in prefetched_issues_list
        }
        needed_project_ids = {
            issue.project_id
            for issue in prefetched_issues_list
        }
        prefetched_projects = services.project.GetProjects(
            mr.cnxn, needed_project_ids)
        prefetched_configs = services.config.GetProjectConfigs(
            mr.cnxn, needed_project_ids)
        viewable_issues_list = tracker_helpers.FilterOutNonViewableIssues(
            mr.auth.effective_ids, mr.auth.user_pb, prefetched_projects,
            prefetched_configs, prefetched_issues_list)
        viewable_iids = {issue.issue_id for issue in viewable_issues_list}
        comments = [c for c in comments if c.issue_id in viewable_iids]
        if ascending:
            comments.reverse()

    amendment_user_ids = []
    for comment in comments:
        for amendment in comment.amendments:
            amendment_user_ids.extend(amendment.added_user_ids)
            amendment_user_ids.extend(amendment.removed_user_ids)

    users_by_id = framework_views.MakeAllUserViews(
        mr.cnxn, services.user, [c.user_id for c in comments],
        amendment_user_ids)
    framework_views.RevealAllEmailsToMembers(mr.auth, mr.project, users_by_id)

    num_results_returned = len(comments)
    displayed_activities = comments[:UPDATES_PER_PAGE]

    if not num_results_returned:
        updates_data['no_activities'] = ezt.boolean(True)
        return updates_data

    # Get all referenced artifacts first
    all_ref_artifacts = None
    if autolink is not None:
        content_list = []
        for activity in comments:
            content_list.append(activity.content)

        all_ref_artifacts = autolink.GetAllReferencedArtifacts(
            mr, content_list)

    # Now process content and gather activities
    today = []
    yesterday = []
    pastweek = []
    pastmonth = []
    thisyear = []
    older = []

    with mr.profiler.Phase('rendering activities'):
        for activity in displayed_activities:
            entry = ActivityView(activity,
                                 mr,
                                 prefetched_issues,
                                 users_by_id,
                                 prefetched_projects,
                                 prefetched_configs,
                                 autolink=autolink,
                                 all_ref_artifacts=all_ref_artifacts,
                                 ending=ending,
                                 highlight=highlight)

            if entry.date_bucket == 'Today':
                today.append(entry)
            elif entry.date_bucket == 'Yesterday':
                yesterday.append(entry)
            elif entry.date_bucket == 'Last 7 days':
                pastweek.append(entry)
            elif entry.date_bucket == 'Last 30 days':
                pastmonth.append(entry)
            elif entry.date_bucket == 'Earlier this year':
                thisyear.append(entry)
            elif entry.date_bucket == 'Before this year':
                older.append(entry)

    new_after = None
    new_before = None
    if displayed_activities:
        new_after = displayed_activities[0].timestamp
        new_before = displayed_activities[-1].timestamp

    prev_url = None
    next_url = None
    if updates_page_url:
        list_servlet_rel_url = updates_page_url.split('/')[-1]
        recognized_params = [(name, mr.GetParam(name))
                             for name in framework_helpers.RECOGNIZED_PARAMS]
        if displayed_activities and (mr.before or mr.after):
            prev_url = framework_helpers.FormatURL(recognized_params,
                                                   list_servlet_rel_url,
                                                   after=new_after)
        if mr.after or len(comments) > UPDATES_PER_PAGE:
            next_url = framework_helpers.FormatURL(recognized_params,
                                                   list_servlet_rel_url,
                                                   before=new_before)

    if prev_url or next_url:
        pagination = template_helpers.EZTItem(start=None,
                                              last=None,
                                              prev_url=prev_url,
                                              next_url=next_url,
                                              reload_url=None,
                                              visible=ezt.boolean(True),
                                              total_count=None)
    else:
        pagination = None

    updates_data.update({
        'no_activities':
        ezt.boolean(False),
        'pagination':
        pagination,
        'updates_data':
        template_helpers.EZTItem(today=today,
                                 yesterday=yesterday,
                                 pastweek=pastweek,
                                 pastmonth=pastmonth,
                                 thisyear=thisyear,
                                 older=older),
    })

    return updates_data
Ejemplo n.º 17
0
    def HandleRequest(self, mr):
        hotlist_id = mr.GetIntParam('hotlist_id')
        list_url = None
        with work_env.WorkEnv(mr, self.services) as we:
            if not _ShouldShowFlipper(mr, self.services):
                return {}
            issue = we.GetIssueByLocalID(mr.project_id,
                                         mr.local_id,
                                         use_cache=False)
            hotlist = None

            if hotlist_id:
                hotlist = self.services.features.GetHotlist(
                    mr.cnxn, hotlist_id)

                if not features_bizobj.IssueIsInHotlist(
                        hotlist, issue.issue_id):
                    raise exceptions.InvalidHotlistException()

                if not permissions.CanViewHotlist(mr.auth.effective_ids,
                                                  mr.perms, hotlist):
                    raise permissions.PermissionException()

                (prev_iid, cur_index, next_iid,
                 total_count) = we.GetIssuePositionInHotlist(issue, hotlist)
            else:
                (prev_iid, cur_index, next_iid,
                 total_count) = we.FindIssuePositionInSearch(issue)

            config = we.GetProjectConfig(self.mr.project_id)

            if hotlist:
                mr.ComputeColSpec(hotlist)
            else:
                mr.ComputeColSpec(config)

            list_url = _ComputeBackToListURL(mr, issue, config, hotlist,
                                             self.services)

        prev_url = None
        next_url = None

        recognized_params = [(name, mr.GetParam(name))
                             for name in framework_helpers.RECOGNIZED_PARAMS]
        if prev_iid:
            prev_issue = we.services.issue.GetIssue(mr.cnxn, prev_iid)
            path = '/p/%s%s' % (prev_issue.project_name, urls.ISSUE_DETAIL)
            prev_url = framework_helpers.FormatURL(recognized_params,
                                                   path,
                                                   id=prev_issue.local_id)

        if next_iid:
            next_issue = we.services.issue.GetIssue(mr.cnxn, next_iid)
            path = '/p/%s%s' % (next_issue.project_name, urls.ISSUE_DETAIL)
            next_url = framework_helpers.FormatURL(recognized_params,
                                                   path,
                                                   id=next_issue.local_id)

        return {
            'prev_iid': prev_iid,
            'prev_url': prev_url,
            'cur_index': cur_index,
            'next_iid': next_iid,
            'next_url': next_url,
            'list_url': list_url,
            'total_count': total_count,
        }
 def testFormatURL(self):
   mr = testing_helpers.MakeMonorailRequest()
   path = '/dude/wheres/my/car'
   url = framework_helpers.FormatURL(mr, path)
   self.assertEqual(path, url)