Revision 1298:4f746d8966dd .svn/pristine/45
| .svn/pristine/45/4537efe545b75bf585f6f1b2f82d7cbf9d1195c2.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2013 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
module Redmine |
|
| 19 |
module Hook |
|
| 20 |
@@listener_classes = [] |
|
| 21 |
@@listeners = nil |
|
| 22 |
@@hook_listeners = {}
|
|
| 23 |
|
|
| 24 |
class << self |
|
| 25 |
# Adds a listener class. |
|
| 26 |
# Automatically called when a class inherits from Redmine::Hook::Listener. |
|
| 27 |
def add_listener(klass) |
|
| 28 |
raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton) |
|
| 29 |
@@listener_classes << klass |
|
| 30 |
clear_listeners_instances |
|
| 31 |
end |
|
| 32 |
|
|
| 33 |
# Returns all the listerners instances. |
|
| 34 |
def listeners |
|
| 35 |
@@listeners ||= @@listener_classes.collect {|listener| listener.instance}
|
|
| 36 |
end |
|
| 37 |
|
|
| 38 |
# Returns the listeners instances for the given hook. |
|
| 39 |
def hook_listeners(hook) |
|
| 40 |
@@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
|
|
| 41 |
end |
|
| 42 |
|
|
| 43 |
# Clears all the listeners. |
|
| 44 |
def clear_listeners |
|
| 45 |
@@listener_classes = [] |
|
| 46 |
clear_listeners_instances |
|
| 47 |
end |
|
| 48 |
|
|
| 49 |
# Clears all the listeners instances. |
|
| 50 |
def clear_listeners_instances |
|
| 51 |
@@listeners = nil |
|
| 52 |
@@hook_listeners = {}
|
|
| 53 |
end |
|
| 54 |
|
|
| 55 |
# Calls a hook. |
|
| 56 |
# Returns the listeners response. |
|
| 57 |
def call_hook(hook, context={})
|
|
| 58 |
[].tap do |response| |
|
| 59 |
hls = hook_listeners(hook) |
|
| 60 |
if hls.any? |
|
| 61 |
hls.each {|listener| response << listener.send(hook, context)}
|
|
| 62 |
end |
|
| 63 |
end |
|
| 64 |
end |
|
| 65 |
end |
|
| 66 |
|
|
| 67 |
# Base class for hook listeners. |
|
| 68 |
class Listener |
|
| 69 |
include Singleton |
|
| 70 |
include Redmine::I18n |
|
| 71 |
|
|
| 72 |
# Registers the listener |
|
| 73 |
def self.inherited(child) |
|
| 74 |
Redmine::Hook.add_listener(child) |
|
| 75 |
super |
|
| 76 |
end |
|
| 77 |
|
|
| 78 |
end |
|
| 79 |
|
|
| 80 |
# Listener class used for views hooks. |
|
| 81 |
# Listeners that inherit this class will include various helpers by default. |
|
| 82 |
class ViewListener < Listener |
|
| 83 |
include ERB::Util |
|
| 84 |
include ActionView::Helpers::TagHelper |
|
| 85 |
include ActionView::Helpers::FormHelper |
|
| 86 |
include ActionView::Helpers::FormTagHelper |
|
| 87 |
include ActionView::Helpers::FormOptionsHelper |
|
| 88 |
include ActionView::Helpers::JavaScriptHelper |
|
| 89 |
include ActionView::Helpers::NumberHelper |
|
| 90 |
include ActionView::Helpers::UrlHelper |
|
| 91 |
include ActionView::Helpers::AssetTagHelper |
|
| 92 |
include ActionView::Helpers::TextHelper |
|
| 93 |
include Rails.application.routes.url_helpers |
|
| 94 |
include ApplicationHelper |
|
| 95 |
|
|
| 96 |
# Default to creating links using only the path. Subclasses can |
|
| 97 |
# change this default as needed |
|
| 98 |
def self.default_url_options |
|
| 99 |
{:only_path => true }
|
|
| 100 |
end |
|
| 101 |
|
|
| 102 |
# Helper method to directly render a partial using the context: |
|
| 103 |
# |
|
| 104 |
# class MyHook < Redmine::Hook::ViewListener |
|
| 105 |
# render_on :view_issues_show_details_bottom, :partial => "show_more_data" |
|
| 106 |
# end |
|
| 107 |
# |
|
| 108 |
def self.render_on(hook, options={})
|
|
| 109 |
define_method hook do |context| |
|
| 110 |
if context[:hook_caller].respond_to?(:render) |
|
| 111 |
context[:hook_caller].send(:render, {:locals => context}.merge(options))
|
|
| 112 |
elsif context[:controller].is_a?(ActionController::Base) |
|
| 113 |
context[:controller].send(:render_to_string, {:locals => context}.merge(options))
|
|
| 114 |
else |
|
| 115 |
raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}"
|
|
| 116 |
end |
|
| 117 |
end |
|
| 118 |
end |
|
| 119 |
|
|
| 120 |
def controller |
|
| 121 |
nil |
|
| 122 |
end |
|
| 123 |
|
|
| 124 |
def config |
|
| 125 |
ActionController::Base.config |
|
| 126 |
end |
|
| 127 |
end |
|
| 128 |
|
|
| 129 |
# Helper module included in ApplicationHelper and ActionController so that |
|
| 130 |
# hooks can be called in views like this: |
|
| 131 |
# |
|
| 132 |
# <%= call_hook(:some_hook) %> |
|
| 133 |
# <%= call_hook(:another_hook, :foo => 'bar') %> |
|
| 134 |
# |
|
| 135 |
# Or in controllers like: |
|
| 136 |
# call_hook(:some_hook) |
|
| 137 |
# call_hook(:another_hook, :foo => 'bar') |
|
| 138 |
# |
|
| 139 |
# Hooks added to views will be concatenated into a string. Hooks added to |
|
| 140 |
# controllers will return an array of results. |
|
| 141 |
# |
|
| 142 |
# Several objects are automatically added to the call context: |
|
| 143 |
# |
|
| 144 |
# * project => current project |
|
| 145 |
# * request => Request instance |
|
| 146 |
# * controller => current Controller instance |
|
| 147 |
# * hook_caller => object that called the hook |
|
| 148 |
# |
|
| 149 |
module Helper |
|
| 150 |
def call_hook(hook, context={})
|
|
| 151 |
if is_a?(ActionController::Base) |
|
| 152 |
default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self}
|
|
| 153 |
Redmine::Hook.call_hook(hook, default_context.merge(context)) |
|
| 154 |
else |
|
| 155 |
default_context = { :project => @project, :hook_caller => self }
|
|
| 156 |
default_context[:controller] = controller if respond_to?(:controller) |
|
| 157 |
default_context[:request] = request if respond_to?(:request) |
|
| 158 |
Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe
|
|
| 159 |
end |
|
| 160 |
end |
|
| 161 |
end |
|
| 162 |
end |
|
| 163 |
end |
|
| 164 |
|
|
| 165 |
ApplicationHelper.send(:include, Redmine::Hook::Helper) |
|
| 166 |
ActionController::Base.send(:include, Redmine::Hook::Helper) |
|
| .svn/pristine/45/4541853cf8b57a89416ab78d9c5650a0f0ed5774.svn-base | ||
|---|---|---|
| 1 |
class AppAndPluginController < ApplicationController |
|
| 2 |
def an_action |
|
| 3 |
render_class_and_action 'from beta_plugin' |
|
| 4 |
end |
|
| 5 |
end |
|
| .svn/pristine/45/45481eeadf5fe7268354b5aabc0c5767470f2390.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2013 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
class Wiki < ActiveRecord::Base |
|
| 19 |
include Redmine::SafeAttributes |
|
| 20 |
belongs_to :project |
|
| 21 |
has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title' |
|
| 22 |
has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all |
|
| 23 |
|
|
| 24 |
acts_as_watchable |
|
| 25 |
|
|
| 26 |
validates_presence_of :start_page |
|
| 27 |
validates_format_of :start_page, :with => /\A[^,\.\/\?\;\|\:]*\z/ |
|
| 28 |
|
|
| 29 |
safe_attributes 'start_page' |
|
| 30 |
|
|
| 31 |
def visible?(user=User.current) |
|
| 32 |
!user.nil? && user.allowed_to?(:view_wiki_pages, project) |
|
| 33 |
end |
|
| 34 |
|
|
| 35 |
# Returns the wiki page that acts as the sidebar content |
|
| 36 |
# or nil if no such page exists |
|
| 37 |
def sidebar |
|
| 38 |
@sidebar ||= find_page('Sidebar', :with_redirect => false)
|
|
| 39 |
end |
|
| 40 |
|
|
| 41 |
# find the page with the given title |
|
| 42 |
# if page doesn't exist, return a new page |
|
| 43 |
def find_or_new_page(title) |
|
| 44 |
title = start_page if title.blank? |
|
| 45 |
find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title)) |
|
| 46 |
end |
|
| 47 |
|
|
| 48 |
# find the page with the given title |
|
| 49 |
def find_page(title, options = {})
|
|
| 50 |
@page_found_with_redirect = false |
|
| 51 |
title = start_page if title.blank? |
|
| 52 |
title = Wiki.titleize(title) |
|
| 53 |
page = pages.first(:conditions => ["LOWER(title) = LOWER(?)", title]) |
|
| 54 |
if !page && !(options[:with_redirect] == false) |
|
| 55 |
# search for a redirect |
|
| 56 |
redirect = redirects.first(:conditions => ["LOWER(title) = LOWER(?)", title]) |
|
| 57 |
if redirect |
|
| 58 |
page = find_page(redirect.redirects_to, :with_redirect => false) |
|
| 59 |
@page_found_with_redirect = true |
|
| 60 |
end |
|
| 61 |
end |
|
| 62 |
page |
|
| 63 |
end |
|
| 64 |
|
|
| 65 |
# Returns true if the last page was found with a redirect |
|
| 66 |
def page_found_with_redirect? |
|
| 67 |
@page_found_with_redirect |
|
| 68 |
end |
|
| 69 |
|
|
| 70 |
# Finds a page by title |
|
| 71 |
# The given string can be of one of the forms: "title" or "project:title" |
|
| 72 |
# Examples: |
|
| 73 |
# Wiki.find_page("bar", project => foo)
|
|
| 74 |
# Wiki.find_page("foo:bar")
|
|
| 75 |
def self.find_page(title, options = {})
|
|
| 76 |
project = options[:project] |
|
| 77 |
if title.to_s =~ %r{^([^\:]+)\:(.*)$}
|
|
| 78 |
project_identifier, title = $1, $2 |
|
| 79 |
project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) |
|
| 80 |
end |
|
| 81 |
if project && project.wiki |
|
| 82 |
page = project.wiki.find_page(title) |
|
| 83 |
if page && page.content |
|
| 84 |
page |
|
| 85 |
end |
|
| 86 |
end |
|
| 87 |
end |
|
| 88 |
|
|
| 89 |
# turn a string into a valid page title |
|
| 90 |
def self.titleize(title) |
|
| 91 |
# replace spaces with _ and remove unwanted caracters |
|
| 92 |
title = title.gsub(/\s+/, '_').delete(',./?;|:') if title
|
|
| 93 |
# upcase the first letter |
|
| 94 |
title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title |
|
| 95 |
title |
|
| 96 |
end |
|
| 97 |
end |
|
| .svn/pristine/45/456ef3443ff7df8c5dc761c97f3f05ad3eb5ac25.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2011 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require File.expand_path('../../../../../test_helper', __FILE__)
|
|
| 19 |
|
|
| 20 |
class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase |
|
| 21 |
include ApplicationHelper |
|
| 22 |
include ActionView::Helpers::TextHelper |
|
| 23 |
include ActionView::Helpers::SanitizeHelper |
|
| 24 |
extend ActionView::Helpers::SanitizeHelper::ClassMethods |
|
| 25 |
|
|
| 26 |
fixtures :projects, :roles, :enabled_modules, :users, |
|
| 27 |
:repositories, :changesets, |
|
| 28 |
:trackers, :issue_statuses, :issues, |
|
| 29 |
:versions, :documents, |
|
| 30 |
:wikis, :wiki_pages, :wiki_contents, |
|
| 31 |
:boards, :messages, |
|
| 32 |
:attachments |
|
| 33 |
|
|
| 34 |
def setup |
|
| 35 |
super |
|
| 36 |
@project = nil |
|
| 37 |
end |
|
| 38 |
|
|
| 39 |
def teardown |
|
| 40 |
end |
|
| 41 |
|
|
| 42 |
def test_macro_hello_world |
|
| 43 |
text = "{{hello_world}}"
|
|
| 44 |
assert textilizable(text).match(/Hello world!/) |
|
| 45 |
# escaping |
|
| 46 |
text = "!{{hello_world}}"
|
|
| 47 |
assert_equal '<p>{{hello_world}}</p>', textilizable(text)
|
|
| 48 |
end |
|
| 49 |
|
|
| 50 |
def test_macro_include |
|
| 51 |
@project = Project.find(1) |
|
| 52 |
# include a page of the current project wiki |
|
| 53 |
text = "{{include(Another page)}}"
|
|
| 54 |
assert textilizable(text).match(/This is a link to a ticket/) |
|
| 55 |
|
|
| 56 |
@project = nil |
|
| 57 |
# include a page of a specific project wiki |
|
| 58 |
text = "{{include(ecookbook:Another page)}}"
|
|
| 59 |
assert textilizable(text).match(/This is a link to a ticket/) |
|
| 60 |
|
|
| 61 |
text = "{{include(ecookbook:)}}"
|
|
| 62 |
assert textilizable(text).match(/CookBook documentation/) |
|
| 63 |
|
|
| 64 |
text = "{{include(unknowidentifier:somepage)}}"
|
|
| 65 |
assert textilizable(text).match(/Page not found/) |
|
| 66 |
end |
|
| 67 |
|
|
| 68 |
def test_macro_child_pages |
|
| 69 |
expected = "<p><ul class=\"pages-hierarchy\">\n" + |
|
| 70 |
"<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" + |
|
| 71 |
"<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" + |
|
| 72 |
"</ul>\n</p>" |
|
| 73 |
|
|
| 74 |
@project = Project.find(1) |
|
| 75 |
# child pages of the current wiki page |
|
| 76 |
assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
|
|
| 77 |
# child pages of another page |
|
| 78 |
assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
|
|
| 79 |
|
|
| 80 |
@project = Project.find(2) |
|
| 81 |
assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
|
|
| 82 |
end |
|
| 83 |
|
|
| 84 |
def test_macro_child_pages_with_option |
|
| 85 |
expected = "<p><ul class=\"pages-hierarchy\">\n" + |
|
| 86 |
"<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" + |
|
| 87 |
"<ul class=\"pages-hierarchy\">\n" + |
|
| 88 |
"<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" + |
|
| 89 |
"<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" + |
|
| 90 |
"</ul>\n</li>\n</ul>\n</p>" |
|
| 91 |
|
|
| 92 |
@project = Project.find(1) |
|
| 93 |
# child pages of the current wiki page |
|
| 94 |
assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
|
|
| 95 |
# child pages of another page |
|
| 96 |
assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
|
|
| 97 |
|
|
| 98 |
@project = Project.find(2) |
|
| 99 |
assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
|
|
| 100 |
end |
|
| 101 |
end |
|
| .svn/pristine/45/457635320d7cf0fe410b58dcbd4445d01bddd1f8.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2011 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require File.expand_path('../../../test_helper', __FILE__)
|
|
| 19 |
require 'pp' |
|
| 20 |
class ApiTest::UsersTest < ActionController::IntegrationTest |
|
| 21 |
fixtures :users |
|
| 22 |
|
|
| 23 |
def setup |
|
| 24 |
Setting.rest_api_enabled = '1' |
|
| 25 |
end |
|
| 26 |
|
|
| 27 |
context "GET /users" do |
|
| 28 |
should_allow_api_authentication(:get, "/users.xml") |
|
| 29 |
should_allow_api_authentication(:get, "/users.json") |
|
| 30 |
end |
|
| 31 |
|
|
| 32 |
context "GET /users/2" do |
|
| 33 |
context ".xml" do |
|
| 34 |
should "return requested user" do |
|
| 35 |
get '/users/2.xml' |
|
| 36 |
|
|
| 37 |
assert_tag :tag => 'user', |
|
| 38 |
:child => {:tag => 'id', :content => '2'}
|
|
| 39 |
end |
|
| 40 |
end |
|
| 41 |
|
|
| 42 |
context ".json" do |
|
| 43 |
should "return requested user" do |
|
| 44 |
get '/users/2.json' |
|
| 45 |
|
|
| 46 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 47 |
assert_kind_of Hash, json |
|
| 48 |
assert_kind_of Hash, json['user'] |
|
| 49 |
assert_equal 2, json['user']['id'] |
|
| 50 |
end |
|
| 51 |
end |
|
| 52 |
end |
|
| 53 |
|
|
| 54 |
context "GET /users/current" do |
|
| 55 |
context ".xml" do |
|
| 56 |
should "require authentication" do |
|
| 57 |
get '/users/current.xml' |
|
| 58 |
|
|
| 59 |
assert_response 401 |
|
| 60 |
end |
|
| 61 |
|
|
| 62 |
should "return current user" do |
|
| 63 |
get '/users/current.xml', {}, :authorization => credentials('jsmith')
|
|
| 64 |
|
|
| 65 |
assert_tag :tag => 'user', |
|
| 66 |
:child => {:tag => 'id', :content => '2'}
|
|
| 67 |
end |
|
| 68 |
end |
|
| 69 |
end |
|
| 70 |
|
|
| 71 |
context "POST /users" do |
|
| 72 |
context "with valid parameters" do |
|
| 73 |
setup do |
|
| 74 |
@parameters = {:user => {:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', :mail => 'foo@example.net', :password => 'secret', :mail_notification => 'only_assigned'}}
|
|
| 75 |
end |
|
| 76 |
|
|
| 77 |
context ".xml" do |
|
| 78 |
should_allow_api_authentication(:post, |
|
| 79 |
'/users.xml', |
|
| 80 |
{:user => {:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', :mail => 'foo@example.net', :password => 'secret'}},
|
|
| 81 |
{:success_code => :created})
|
|
| 82 |
|
|
| 83 |
should "create a user with the attributes" do |
|
| 84 |
assert_difference('User.count') do
|
|
| 85 |
post '/users.xml', @parameters, :authorization => credentials('admin')
|
|
| 86 |
end |
|
| 87 |
|
|
| 88 |
user = User.first(:order => 'id DESC') |
|
| 89 |
assert_equal 'foo', user.login |
|
| 90 |
assert_equal 'Firstname', user.firstname |
|
| 91 |
assert_equal 'Lastname', user.lastname |
|
| 92 |
assert_equal 'foo@example.net', user.mail |
|
| 93 |
assert_equal 'only_assigned', user.mail_notification |
|
| 94 |
assert !user.admin? |
|
| 95 |
assert user.check_password?('secret')
|
|
| 96 |
|
|
| 97 |
assert_response :created |
|
| 98 |
assert_equal 'application/xml', @response.content_type |
|
| 99 |
assert_tag 'user', :child => {:tag => 'id', :content => user.id.to_s}
|
|
| 100 |
end |
|
| 101 |
end |
|
| 102 |
|
|
| 103 |
context ".json" do |
|
| 104 |
should_allow_api_authentication(:post, |
|
| 105 |
'/users.json', |
|
| 106 |
{:user => {:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', :mail => 'foo@example.net'}},
|
|
| 107 |
{:success_code => :created})
|
|
| 108 |
|
|
| 109 |
should "create a user with the attributes" do |
|
| 110 |
assert_difference('User.count') do
|
|
| 111 |
post '/users.json', @parameters, :authorization => credentials('admin')
|
|
| 112 |
end |
|
| 113 |
|
|
| 114 |
user = User.first(:order => 'id DESC') |
|
| 115 |
assert_equal 'foo', user.login |
|
| 116 |
assert_equal 'Firstname', user.firstname |
|
| 117 |
assert_equal 'Lastname', user.lastname |
|
| 118 |
assert_equal 'foo@example.net', user.mail |
|
| 119 |
assert !user.admin? |
|
| 120 |
|
|
| 121 |
assert_response :created |
|
| 122 |
assert_equal 'application/json', @response.content_type |
|
| 123 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 124 |
assert_kind_of Hash, json |
|
| 125 |
assert_kind_of Hash, json['user'] |
|
| 126 |
assert_equal user.id, json['user']['id'] |
|
| 127 |
end |
|
| 128 |
end |
|
| 129 |
end |
|
| 130 |
|
|
| 131 |
context "with invalid parameters" do |
|
| 132 |
setup do |
|
| 133 |
@parameters = {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}
|
|
| 134 |
end |
|
| 135 |
|
|
| 136 |
context ".xml" do |
|
| 137 |
should "return errors" do |
|
| 138 |
assert_no_difference('User.count') do
|
|
| 139 |
post '/users.xml', @parameters, :authorization => credentials('admin')
|
|
| 140 |
end |
|
| 141 |
|
|
| 142 |
assert_response :unprocessable_entity |
|
| 143 |
assert_equal 'application/xml', @response.content_type |
|
| 144 |
assert_tag 'errors', :child => {:tag => 'error', :content => "First name can't be blank"}
|
|
| 145 |
end |
|
| 146 |
end |
|
| 147 |
|
|
| 148 |
context ".json" do |
|
| 149 |
should "return errors" do |
|
| 150 |
assert_no_difference('User.count') do
|
|
| 151 |
post '/users.json', @parameters, :authorization => credentials('admin')
|
|
| 152 |
end |
|
| 153 |
|
|
| 154 |
assert_response :unprocessable_entity |
|
| 155 |
assert_equal 'application/json', @response.content_type |
|
| 156 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 157 |
assert_kind_of Hash, json |
|
| 158 |
assert json.has_key?('errors')
|
|
| 159 |
assert_kind_of Array, json['errors'] |
|
| 160 |
end |
|
| 161 |
end |
|
| 162 |
end |
|
| 163 |
end |
|
| 164 |
|
|
| 165 |
context "PUT /users/2" do |
|
| 166 |
context "with valid parameters" do |
|
| 167 |
setup do |
|
| 168 |
@parameters = {:user => {:login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', :mail => 'jsmith@somenet.foo'}}
|
|
| 169 |
end |
|
| 170 |
|
|
| 171 |
context ".xml" do |
|
| 172 |
should_allow_api_authentication(:put, |
|
| 173 |
'/users/2.xml', |
|
| 174 |
{:user => {:login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', :mail => 'jsmith@somenet.foo'}},
|
|
| 175 |
{:success_code => :ok})
|
|
| 176 |
|
|
| 177 |
should "update user with the attributes" do |
|
| 178 |
assert_no_difference('User.count') do
|
|
| 179 |
put '/users/2.xml', @parameters, :authorization => credentials('admin')
|
|
| 180 |
end |
|
| 181 |
|
|
| 182 |
user = User.find(2) |
|
| 183 |
assert_equal 'jsmith', user.login |
|
| 184 |
assert_equal 'John', user.firstname |
|
| 185 |
assert_equal 'Renamed', user.lastname |
|
| 186 |
assert_equal 'jsmith@somenet.foo', user.mail |
|
| 187 |
assert !user.admin? |
|
| 188 |
|
|
| 189 |
assert_response :ok |
|
| 190 |
end |
|
| 191 |
end |
|
| 192 |
|
|
| 193 |
context ".json" do |
|
| 194 |
should_allow_api_authentication(:put, |
|
| 195 |
'/users/2.json', |
|
| 196 |
{:user => {:login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', :mail => 'jsmith@somenet.foo'}},
|
|
| 197 |
{:success_code => :ok})
|
|
| 198 |
|
|
| 199 |
should "update user with the attributes" do |
|
| 200 |
assert_no_difference('User.count') do
|
|
| 201 |
put '/users/2.json', @parameters, :authorization => credentials('admin')
|
|
| 202 |
end |
|
| 203 |
|
|
| 204 |
user = User.find(2) |
|
| 205 |
assert_equal 'jsmith', user.login |
|
| 206 |
assert_equal 'John', user.firstname |
|
| 207 |
assert_equal 'Renamed', user.lastname |
|
| 208 |
assert_equal 'jsmith@somenet.foo', user.mail |
|
| 209 |
assert !user.admin? |
|
| 210 |
|
|
| 211 |
assert_response :ok |
|
| 212 |
end |
|
| 213 |
end |
|
| 214 |
end |
|
| 215 |
|
|
| 216 |
context "with invalid parameters" do |
|
| 217 |
setup do |
|
| 218 |
@parameters = {:user => {:login => 'jsmith', :firstname => '', :lastname => 'Lastname', :mail => 'foo'}}
|
|
| 219 |
end |
|
| 220 |
|
|
| 221 |
context ".xml" do |
|
| 222 |
should "return errors" do |
|
| 223 |
assert_no_difference('User.count') do
|
|
| 224 |
put '/users/2.xml', @parameters, :authorization => credentials('admin')
|
|
| 225 |
end |
|
| 226 |
|
|
| 227 |
assert_response :unprocessable_entity |
|
| 228 |
assert_equal 'application/xml', @response.content_type |
|
| 229 |
assert_tag 'errors', :child => {:tag => 'error', :content => "First name can't be blank"}
|
|
| 230 |
end |
|
| 231 |
end |
|
| 232 |
|
|
| 233 |
context ".json" do |
|
| 234 |
should "return errors" do |
|
| 235 |
assert_no_difference('User.count') do
|
|
| 236 |
put '/users/2.json', @parameters, :authorization => credentials('admin')
|
|
| 237 |
end |
|
| 238 |
|
|
| 239 |
assert_response :unprocessable_entity |
|
| 240 |
assert_equal 'application/json', @response.content_type |
|
| 241 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 242 |
assert_kind_of Hash, json |
|
| 243 |
assert json.has_key?('errors')
|
|
| 244 |
assert_kind_of Array, json['errors'] |
|
| 245 |
end |
|
| 246 |
end |
|
| 247 |
end |
|
| 248 |
end |
|
| 249 |
|
|
| 250 |
context "DELETE /users/2" do |
|
| 251 |
context ".xml" do |
|
| 252 |
should_allow_api_authentication(:delete, |
|
| 253 |
'/users/2.xml', |
|
| 254 |
{},
|
|
| 255 |
{:success_code => :ok})
|
|
| 256 |
|
|
| 257 |
should "delete user" do |
|
| 258 |
assert_difference('User.count', -1) do
|
|
| 259 |
delete '/users/2.xml', {}, :authorization => credentials('admin')
|
|
| 260 |
end |
|
| 261 |
|
|
| 262 |
assert_response :ok |
|
| 263 |
end |
|
| 264 |
end |
|
| 265 |
|
|
| 266 |
context ".json" do |
|
| 267 |
should_allow_api_authentication(:delete, |
|
| 268 |
'/users/2.xml', |
|
| 269 |
{},
|
|
| 270 |
{:success_code => :ok})
|
|
| 271 |
|
|
| 272 |
should "delete user" do |
|
| 273 |
assert_difference('User.count', -1) do
|
|
| 274 |
delete '/users/2.json', {}, :authorization => credentials('admin')
|
|
| 275 |
end |
|
| 276 |
|
|
| 277 |
assert_response :ok |
|
| 278 |
end |
|
| 279 |
end |
|
| 280 |
end |
|
| 281 |
|
|
| 282 |
def credentials(user, password=nil) |
|
| 283 |
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) |
|
| 284 |
end |
|
| 285 |
end |
|
| .svn/pristine/45/4589164e770ce01b12657f2cebf30982504c9054.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2013 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require File.expand_path('../../test_helper', __FILE__)
|
|
| 19 |
|
|
| 20 |
class QueryTest < ActiveSupport::TestCase |
|
| 21 |
include Redmine::I18n |
|
| 22 |
|
|
| 23 |
fixtures :projects, :enabled_modules, :users, :members, |
|
| 24 |
:member_roles, :roles, :trackers, :issue_statuses, |
|
| 25 |
:issue_categories, :enumerations, :issues, |
|
| 26 |
:watchers, :custom_fields, :custom_values, :versions, |
|
| 27 |
:queries, |
|
| 28 |
:projects_trackers, |
|
| 29 |
:custom_fields_trackers |
|
| 30 |
|
|
| 31 |
def test_available_filters_should_be_ordered |
|
| 32 |
query = IssueQuery.new |
|
| 33 |
assert_equal 0, query.available_filters.keys.index('status_id')
|
|
| 34 |
end |
|
| 35 |
|
|
| 36 |
def test_custom_fields_for_all_projects_should_be_available_in_global_queries |
|
| 37 |
query = IssueQuery.new(:project => nil, :name => '_') |
|
| 38 |
assert query.available_filters.has_key?('cf_1')
|
|
| 39 |
assert !query.available_filters.has_key?('cf_3')
|
|
| 40 |
end |
|
| 41 |
|
|
| 42 |
def test_system_shared_versions_should_be_available_in_global_queries |
|
| 43 |
Version.find(2).update_attribute :sharing, 'system' |
|
| 44 |
query = IssueQuery.new(:project => nil, :name => '_') |
|
| 45 |
assert query.available_filters.has_key?('fixed_version_id')
|
|
| 46 |
assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
|
|
| 47 |
end |
|
| 48 |
|
|
| 49 |
def test_project_filter_in_global_queries |
|
| 50 |
query = IssueQuery.new(:project => nil, :name => '_') |
|
| 51 |
project_filter = query.available_filters["project_id"] |
|
| 52 |
assert_not_nil project_filter |
|
| 53 |
project_ids = project_filter[:values].map{|p| p[1]}
|
|
| 54 |
assert project_ids.include?("1") #public project
|
|
| 55 |
assert !project_ids.include?("2") #private project user cannot see
|
|
| 56 |
end |
|
| 57 |
|
|
| 58 |
def find_issues_with_query(query) |
|
| 59 |
Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( |
|
| 60 |
query.statement |
|
| 61 |
).all |
|
| 62 |
end |
|
| 63 |
|
|
| 64 |
def assert_find_issues_with_query_is_successful(query) |
|
| 65 |
assert_nothing_raised do |
|
| 66 |
find_issues_with_query(query) |
|
| 67 |
end |
|
| 68 |
end |
|
| 69 |
|
|
| 70 |
def assert_query_statement_includes(query, condition) |
|
| 71 |
assert_include condition, query.statement |
|
| 72 |
end |
|
| 73 |
|
|
| 74 |
def assert_query_result(expected, query) |
|
| 75 |
assert_nothing_raised do |
|
| 76 |
assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort |
|
| 77 |
assert_equal expected.size, query.issue_count |
|
| 78 |
end |
|
| 79 |
end |
|
| 80 |
|
|
| 81 |
def test_query_should_allow_shared_versions_for_a_project_query |
|
| 82 |
subproject_version = Version.find(4) |
|
| 83 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 84 |
query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
|
|
| 85 |
|
|
| 86 |
assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
|
|
| 87 |
end |
|
| 88 |
|
|
| 89 |
def test_query_with_multiple_custom_fields |
|
| 90 |
query = IssueQuery.find(1) |
|
| 91 |
assert query.valid? |
|
| 92 |
assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
|
|
| 93 |
issues = find_issues_with_query(query) |
|
| 94 |
assert_equal 1, issues.length |
|
| 95 |
assert_equal Issue.find(3), issues.first |
|
| 96 |
end |
|
| 97 |
|
|
| 98 |
def test_operator_none |
|
| 99 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 100 |
query.add_filter('fixed_version_id', '!*', [''])
|
|
| 101 |
query.add_filter('cf_1', '!*', [''])
|
|
| 102 |
assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
|
|
| 103 |
assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
|
|
| 104 |
find_issues_with_query(query) |
|
| 105 |
end |
|
| 106 |
|
|
| 107 |
def test_operator_none_for_integer |
|
| 108 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 109 |
query.add_filter('estimated_hours', '!*', [''])
|
|
| 110 |
issues = find_issues_with_query(query) |
|
| 111 |
assert !issues.empty? |
|
| 112 |
assert issues.all? {|i| !i.estimated_hours}
|
|
| 113 |
end |
|
| 114 |
|
|
| 115 |
def test_operator_none_for_date |
|
| 116 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 117 |
query.add_filter('start_date', '!*', [''])
|
|
| 118 |
issues = find_issues_with_query(query) |
|
| 119 |
assert !issues.empty? |
|
| 120 |
assert issues.all? {|i| i.start_date.nil?}
|
|
| 121 |
end |
|
| 122 |
|
|
| 123 |
def test_operator_none_for_string_custom_field |
|
| 124 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 125 |
query.add_filter('cf_2', '!*', [''])
|
|
| 126 |
assert query.has_filter?('cf_2')
|
|
| 127 |
issues = find_issues_with_query(query) |
|
| 128 |
assert !issues.empty? |
|
| 129 |
assert issues.all? {|i| i.custom_field_value(2).blank?}
|
|
| 130 |
end |
|
| 131 |
|
|
| 132 |
def test_operator_all |
|
| 133 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 134 |
query.add_filter('fixed_version_id', '*', [''])
|
|
| 135 |
query.add_filter('cf_1', '*', [''])
|
|
| 136 |
assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
|
|
| 137 |
assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
|
|
| 138 |
find_issues_with_query(query) |
|
| 139 |
end |
|
| 140 |
|
|
| 141 |
def test_operator_all_for_date |
|
| 142 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 143 |
query.add_filter('start_date', '*', [''])
|
|
| 144 |
issues = find_issues_with_query(query) |
|
| 145 |
assert !issues.empty? |
|
| 146 |
assert issues.all? {|i| i.start_date.present?}
|
|
| 147 |
end |
|
| 148 |
|
|
| 149 |
def test_operator_all_for_string_custom_field |
|
| 150 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 151 |
query.add_filter('cf_2', '*', [''])
|
|
| 152 |
assert query.has_filter?('cf_2')
|
|
| 153 |
issues = find_issues_with_query(query) |
|
| 154 |
assert !issues.empty? |
|
| 155 |
assert issues.all? {|i| i.custom_field_value(2).present?}
|
|
| 156 |
end |
|
| 157 |
|
|
| 158 |
def test_numeric_filter_should_not_accept_non_numeric_values |
|
| 159 |
query = IssueQuery.new(:name => '_') |
|
| 160 |
query.add_filter('estimated_hours', '=', ['a'])
|
|
| 161 |
|
|
| 162 |
assert query.has_filter?('estimated_hours')
|
|
| 163 |
assert !query.valid? |
|
| 164 |
end |
|
| 165 |
|
|
| 166 |
def test_operator_is_on_float |
|
| 167 |
Issue.update_all("estimated_hours = 171.2", "id=2")
|
|
| 168 |
|
|
| 169 |
query = IssueQuery.new(:name => '_') |
|
| 170 |
query.add_filter('estimated_hours', '=', ['171.20'])
|
|
| 171 |
issues = find_issues_with_query(query) |
|
| 172 |
assert_equal 1, issues.size |
|
| 173 |
assert_equal 2, issues.first.id |
|
| 174 |
end |
|
| 175 |
|
|
| 176 |
def test_operator_is_on_integer_custom_field |
|
| 177 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) |
|
| 178 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') |
|
| 179 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') |
|
| 180 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
| 181 |
|
|
| 182 |
query = IssueQuery.new(:name => '_') |
|
| 183 |
query.add_filter("cf_#{f.id}", '=', ['12'])
|
|
| 184 |
issues = find_issues_with_query(query) |
|
| 185 |
assert_equal 1, issues.size |
|
| 186 |
assert_equal 2, issues.first.id |
|
| 187 |
end |
|
| 188 |
|
|
| 189 |
def test_operator_is_on_integer_custom_field_should_accept_negative_value |
|
| 190 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) |
|
| 191 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') |
|
| 192 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12') |
|
| 193 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
| 194 |
|
|
| 195 |
query = IssueQuery.new(:name => '_') |
|
| 196 |
query.add_filter("cf_#{f.id}", '=', ['-12'])
|
|
| 197 |
assert query.valid? |
|
| 198 |
issues = find_issues_with_query(query) |
|
| 199 |
assert_equal 1, issues.size |
|
| 200 |
assert_equal 2, issues.first.id |
|
| 201 |
end |
|
| 202 |
|
|
| 203 |
def test_operator_is_on_float_custom_field |
|
| 204 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) |
|
| 205 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') |
|
| 206 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7') |
|
| 207 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
| 208 |
|
|
| 209 |
query = IssueQuery.new(:name => '_') |
|
| 210 |
query.add_filter("cf_#{f.id}", '=', ['12.7'])
|
|
| 211 |
issues = find_issues_with_query(query) |
|
| 212 |
assert_equal 1, issues.size |
|
| 213 |
assert_equal 2, issues.first.id |
|
| 214 |
end |
|
| 215 |
|
|
| 216 |
def test_operator_is_on_float_custom_field_should_accept_negative_value |
|
| 217 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) |
|
| 218 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') |
|
| 219 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7') |
|
| 220 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
| 221 |
|
|
| 222 |
query = IssueQuery.new(:name => '_') |
|
| 223 |
query.add_filter("cf_#{f.id}", '=', ['-12.7'])
|
|
| 224 |
assert query.valid? |
|
| 225 |
issues = find_issues_with_query(query) |
|
| 226 |
assert_equal 1, issues.size |
|
| 227 |
assert_equal 2, issues.first.id |
|
| 228 |
end |
|
| 229 |
|
|
| 230 |
def test_operator_is_on_multi_list_custom_field |
|
| 231 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, |
|
| 232 |
:possible_values => ['value1', 'value2', 'value3'], :multiple => true) |
|
| 233 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') |
|
| 234 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') |
|
| 235 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') |
|
| 236 |
|
|
| 237 |
query = IssueQuery.new(:name => '_') |
|
| 238 |
query.add_filter("cf_#{f.id}", '=', ['value1'])
|
|
| 239 |
issues = find_issues_with_query(query) |
|
| 240 |
assert_equal [1, 3], issues.map(&:id).sort |
|
| 241 |
|
|
| 242 |
query = IssueQuery.new(:name => '_') |
|
| 243 |
query.add_filter("cf_#{f.id}", '=', ['value2'])
|
|
| 244 |
issues = find_issues_with_query(query) |
|
| 245 |
assert_equal [1], issues.map(&:id).sort |
|
| 246 |
end |
|
| 247 |
|
|
| 248 |
def test_operator_is_not_on_multi_list_custom_field |
|
| 249 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, |
|
| 250 |
:possible_values => ['value1', 'value2', 'value3'], :multiple => true) |
|
| 251 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') |
|
| 252 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') |
|
| 253 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') |
|
| 254 |
|
|
| 255 |
query = IssueQuery.new(:name => '_') |
|
| 256 |
query.add_filter("cf_#{f.id}", '!', ['value1'])
|
|
| 257 |
issues = find_issues_with_query(query) |
|
| 258 |
assert !issues.map(&:id).include?(1) |
|
| 259 |
assert !issues.map(&:id).include?(3) |
|
| 260 |
|
|
| 261 |
query = IssueQuery.new(:name => '_') |
|
| 262 |
query.add_filter("cf_#{f.id}", '!', ['value2'])
|
|
| 263 |
issues = find_issues_with_query(query) |
|
| 264 |
assert !issues.map(&:id).include?(1) |
|
| 265 |
assert issues.map(&:id).include?(3) |
|
| 266 |
end |
|
| 267 |
|
|
| 268 |
def test_operator_is_on_is_private_field |
|
| 269 |
# is_private filter only available for those who can set issues private |
|
| 270 |
User.current = User.find(2) |
|
| 271 |
|
|
| 272 |
query = IssueQuery.new(:name => '_') |
|
| 273 |
assert query.available_filters.key?('is_private')
|
|
| 274 |
|
|
| 275 |
query.add_filter("is_private", '=', ['1'])
|
|
| 276 |
issues = find_issues_with_query(query) |
|
| 277 |
assert issues.any? |
|
| 278 |
assert_nil issues.detect {|issue| !issue.is_private?}
|
|
| 279 |
ensure |
|
| 280 |
User.current = nil |
|
| 281 |
end |
|
| 282 |
|
|
| 283 |
def test_operator_is_not_on_is_private_field |
|
| 284 |
# is_private filter only available for those who can set issues private |
|
| 285 |
User.current = User.find(2) |
|
| 286 |
|
|
| 287 |
query = IssueQuery.new(:name => '_') |
|
| 288 |
assert query.available_filters.key?('is_private')
|
|
| 289 |
|
|
| 290 |
query.add_filter("is_private", '!', ['1'])
|
|
| 291 |
issues = find_issues_with_query(query) |
|
| 292 |
assert issues.any? |
|
| 293 |
assert_nil issues.detect {|issue| issue.is_private?}
|
|
| 294 |
ensure |
|
| 295 |
User.current = nil |
|
| 296 |
end |
|
| 297 |
|
|
| 298 |
def test_operator_greater_than |
|
| 299 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 300 |
query.add_filter('done_ratio', '>=', ['40'])
|
|
| 301 |
assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
|
|
| 302 |
find_issues_with_query(query) |
|
| 303 |
end |
|
| 304 |
|
|
| 305 |
def test_operator_greater_than_a_float |
|
| 306 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 307 |
query.add_filter('estimated_hours', '>=', ['40.5'])
|
|
| 308 |
assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
|
|
| 309 |
find_issues_with_query(query) |
|
| 310 |
end |
|
| 311 |
|
|
| 312 |
def test_operator_greater_than_on_int_custom_field |
|
| 313 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) |
|
| 314 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') |
|
| 315 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') |
|
| 316 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
| 317 |
|
|
| 318 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 319 |
query.add_filter("cf_#{f.id}", '>=', ['8'])
|
|
| 320 |
issues = find_issues_with_query(query) |
|
| 321 |
assert_equal 1, issues.size |
|
| 322 |
assert_equal 2, issues.first.id |
|
| 323 |
end |
|
| 324 |
|
|
| 325 |
def test_operator_lesser_than |
|
| 326 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 327 |
query.add_filter('done_ratio', '<=', ['30'])
|
|
| 328 |
assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
|
|
| 329 |
find_issues_with_query(query) |
|
| 330 |
end |
|
| 331 |
|
|
| 332 |
def test_operator_lesser_than_on_custom_field |
|
| 333 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) |
|
| 334 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 335 |
query.add_filter("cf_#{f.id}", '<=', ['30'])
|
|
| 336 |
assert_match /CAST.+ <= 30\.0/, query.statement |
|
| 337 |
find_issues_with_query(query) |
|
| 338 |
end |
|
| 339 |
|
|
| 340 |
def test_operator_lesser_than_on_date_custom_field |
|
| 341 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true) |
|
| 342 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11') |
|
| 343 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14') |
|
| 344 |
CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
| 345 |
|
|
| 346 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 347 |
query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
|
|
| 348 |
issue_ids = find_issues_with_query(query).map(&:id) |
|
| 349 |
assert_include 1, issue_ids |
|
| 350 |
assert_not_include 2, issue_ids |
|
| 351 |
assert_not_include 3, issue_ids |
|
| 352 |
end |
|
| 353 |
|
|
| 354 |
def test_operator_between |
|
| 355 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 356 |
query.add_filter('done_ratio', '><', ['30', '40'])
|
|
| 357 |
assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
|
|
| 358 |
find_issues_with_query(query) |
|
| 359 |
end |
|
| 360 |
|
|
| 361 |
def test_operator_between_on_custom_field |
|
| 362 |
f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) |
|
| 363 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 364 |
query.add_filter("cf_#{f.id}", '><', ['30', '40'])
|
|
| 365 |
assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement |
|
| 366 |
find_issues_with_query(query) |
|
| 367 |
end |
|
| 368 |
|
|
| 369 |
def test_date_filter_should_not_accept_non_date_values |
|
| 370 |
query = IssueQuery.new(:name => '_') |
|
| 371 |
query.add_filter('created_on', '=', ['a'])
|
|
| 372 |
|
|
| 373 |
assert query.has_filter?('created_on')
|
|
| 374 |
assert !query.valid? |
|
| 375 |
end |
|
| 376 |
|
|
| 377 |
def test_date_filter_should_not_accept_invalid_date_values |
|
| 378 |
query = IssueQuery.new(:name => '_') |
|
| 379 |
query.add_filter('created_on', '=', ['2011-01-34'])
|
|
| 380 |
|
|
| 381 |
assert query.has_filter?('created_on')
|
|
| 382 |
assert !query.valid? |
|
| 383 |
end |
|
| 384 |
|
|
| 385 |
def test_relative_date_filter_should_not_accept_non_integer_values |
|
| 386 |
query = IssueQuery.new(:name => '_') |
|
| 387 |
query.add_filter('created_on', '>t-', ['a'])
|
|
| 388 |
|
|
| 389 |
assert query.has_filter?('created_on')
|
|
| 390 |
assert !query.valid? |
|
| 391 |
end |
|
| 392 |
|
|
| 393 |
def test_operator_date_equals |
|
| 394 |
query = IssueQuery.new(:name => '_') |
|
| 395 |
query.add_filter('due_date', '=', ['2011-07-10'])
|
|
| 396 |
assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement |
|
| 397 |
find_issues_with_query(query) |
|
| 398 |
end |
|
| 399 |
|
|
| 400 |
def test_operator_date_lesser_than |
|
| 401 |
query = IssueQuery.new(:name => '_') |
|
| 402 |
query.add_filter('due_date', '<=', ['2011-07-10'])
|
|
| 403 |
assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement |
|
| 404 |
find_issues_with_query(query) |
|
| 405 |
end |
|
| 406 |
|
|
| 407 |
def test_operator_date_greater_than |
|
| 408 |
query = IssueQuery.new(:name => '_') |
|
| 409 |
query.add_filter('due_date', '>=', ['2011-07-10'])
|
|
| 410 |
assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement |
|
| 411 |
find_issues_with_query(query) |
|
| 412 |
end |
|
| 413 |
|
|
| 414 |
def test_operator_date_between |
|
| 415 |
query = IssueQuery.new(:name => '_') |
|
| 416 |
query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
|
|
| 417 |
assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement |
|
| 418 |
find_issues_with_query(query) |
|
| 419 |
end |
|
| 420 |
|
|
| 421 |
def test_operator_in_more_than |
|
| 422 |
Issue.find(7).update_attribute(:due_date, (Date.today + 15)) |
|
| 423 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 424 |
query.add_filter('due_date', '>t+', ['15'])
|
|
| 425 |
issues = find_issues_with_query(query) |
|
| 426 |
assert !issues.empty? |
|
| 427 |
issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
|
|
| 428 |
end |
|
| 429 |
|
|
| 430 |
def test_operator_in_less_than |
|
| 431 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 432 |
query.add_filter('due_date', '<t+', ['15'])
|
|
| 433 |
issues = find_issues_with_query(query) |
|
| 434 |
assert !issues.empty? |
|
| 435 |
issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
|
|
| 436 |
end |
|
| 437 |
|
|
| 438 |
def test_operator_in_the_next_days |
|
| 439 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 440 |
query.add_filter('due_date', '><t+', ['15'])
|
|
| 441 |
issues = find_issues_with_query(query) |
|
| 442 |
assert !issues.empty? |
|
| 443 |
issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
|
|
| 444 |
end |
|
| 445 |
|
|
| 446 |
def test_operator_less_than_ago |
|
| 447 |
Issue.find(7).update_attribute(:due_date, (Date.today - 3)) |
|
| 448 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 449 |
query.add_filter('due_date', '>t-', ['3'])
|
|
| 450 |
issues = find_issues_with_query(query) |
|
| 451 |
assert !issues.empty? |
|
| 452 |
issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
|
|
| 453 |
end |
|
| 454 |
|
|
| 455 |
def test_operator_in_the_past_days |
|
| 456 |
Issue.find(7).update_attribute(:due_date, (Date.today - 3)) |
|
| 457 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 458 |
query.add_filter('due_date', '><t-', ['3'])
|
|
| 459 |
issues = find_issues_with_query(query) |
|
| 460 |
assert !issues.empty? |
|
| 461 |
issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
|
|
| 462 |
end |
|
| 463 |
|
|
| 464 |
def test_operator_more_than_ago |
|
| 465 |
Issue.find(7).update_attribute(:due_date, (Date.today - 10)) |
|
| 466 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 467 |
query.add_filter('due_date', '<t-', ['10'])
|
|
| 468 |
assert query.statement.include?("#{Issue.table_name}.due_date <=")
|
|
| 469 |
issues = find_issues_with_query(query) |
|
| 470 |
assert !issues.empty? |
|
| 471 |
issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
|
|
| 472 |
end |
|
| 473 |
|
|
| 474 |
def test_operator_in |
|
| 475 |
Issue.find(7).update_attribute(:due_date, (Date.today + 2)) |
|
| 476 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 477 |
query.add_filter('due_date', 't+', ['2'])
|
|
| 478 |
issues = find_issues_with_query(query) |
|
| 479 |
assert !issues.empty? |
|
| 480 |
issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
|
|
| 481 |
end |
|
| 482 |
|
|
| 483 |
def test_operator_ago |
|
| 484 |
Issue.find(7).update_attribute(:due_date, (Date.today - 3)) |
|
| 485 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 486 |
query.add_filter('due_date', 't-', ['3'])
|
|
| 487 |
issues = find_issues_with_query(query) |
|
| 488 |
assert !issues.empty? |
|
| 489 |
issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
|
|
| 490 |
end |
|
| 491 |
|
|
| 492 |
def test_operator_today |
|
| 493 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 494 |
query.add_filter('due_date', 't', [''])
|
|
| 495 |
issues = find_issues_with_query(query) |
|
| 496 |
assert !issues.empty? |
|
| 497 |
issues.each {|issue| assert_equal Date.today, issue.due_date}
|
|
| 498 |
end |
|
| 499 |
|
|
| 500 |
def test_operator_this_week_on_date |
|
| 501 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 502 |
query.add_filter('due_date', 'w', [''])
|
|
| 503 |
find_issues_with_query(query) |
|
| 504 |
end |
|
| 505 |
|
|
| 506 |
def test_operator_this_week_on_datetime |
|
| 507 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 508 |
query.add_filter('created_on', 'w', [''])
|
|
| 509 |
find_issues_with_query(query) |
|
| 510 |
end |
|
| 511 |
|
|
| 512 |
def test_operator_contains |
|
| 513 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 514 |
query.add_filter('subject', '~', ['uNable'])
|
|
| 515 |
assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
|
|
| 516 |
result = find_issues_with_query(query) |
|
| 517 |
assert result.empty? |
|
| 518 |
result.each {|issue| assert issue.subject.downcase.include?('unable') }
|
|
| 519 |
end |
|
| 520 |
|
|
| 521 |
def test_range_for_this_week_with_week_starting_on_monday |
|
| 522 |
I18n.locale = :fr |
|
| 523 |
assert_equal '1', I18n.t(:general_first_day_of_week) |
|
| 524 |
|
|
| 525 |
Date.stubs(:today).returns(Date.parse('2011-04-29'))
|
|
| 526 |
|
|
| 527 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 528 |
query.add_filter('due_date', 'w', [''])
|
|
| 529 |
assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
|
|
| 530 |
I18n.locale = :en |
|
| 531 |
end |
|
| 532 |
|
|
| 533 |
def test_range_for_this_week_with_week_starting_on_sunday |
|
| 534 |
I18n.locale = :en |
|
| 535 |
assert_equal '7', I18n.t(:general_first_day_of_week) |
|
| 536 |
|
|
| 537 |
Date.stubs(:today).returns(Date.parse('2011-04-29'))
|
|
| 538 |
|
|
| 539 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 540 |
query.add_filter('due_date', 'w', [''])
|
|
| 541 |
assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
|
|
| 542 |
end |
|
| 543 |
|
|
| 544 |
def test_operator_does_not_contains |
|
| 545 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 546 |
query.add_filter('subject', '!~', ['uNable'])
|
|
| 547 |
assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
|
|
| 548 |
find_issues_with_query(query) |
|
| 549 |
end |
|
| 550 |
|
|
| 551 |
def test_filter_assigned_to_me |
|
| 552 |
user = User.find(2) |
|
| 553 |
group = Group.find(10) |
|
| 554 |
User.current = user |
|
| 555 |
i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user) |
|
| 556 |
i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group) |
|
| 557 |
i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11)) |
|
| 558 |
group.users << user |
|
| 559 |
|
|
| 560 |
query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
|
|
| 561 |
result = query.issues |
|
| 562 |
assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
|
|
| 563 |
|
|
| 564 |
assert result.include?(i1) |
|
| 565 |
assert result.include?(i2) |
|
| 566 |
assert !result.include?(i3) |
|
| 567 |
end |
|
| 568 |
|
|
| 569 |
def test_user_custom_field_filtered_on_me |
|
| 570 |
User.current = User.find(2) |
|
| 571 |
cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) |
|
| 572 |
issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
|
|
| 573 |
issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
|
|
| 574 |
|
|
| 575 |
query = IssueQuery.new(:name => '_', :project => Project.find(1)) |
|
| 576 |
filter = query.available_filters["cf_#{cf.id}"]
|
|
| 577 |
assert_not_nil filter |
|
| 578 |
assert_include 'me', filter[:values].map{|v| v[1]}
|
|
| 579 |
|
|
| 580 |
query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
|
|
| 581 |
result = query.issues |
|
| 582 |
assert_equal 1, result.size |
|
| 583 |
assert_equal issue1, result.first |
|
| 584 |
end |
|
| 585 |
|
|
| 586 |
def test_filter_my_projects |
|
| 587 |
User.current = User.find(2) |
|
| 588 |
query = IssueQuery.new(:name => '_') |
|
| 589 |
filter = query.available_filters['project_id'] |
|
| 590 |
assert_not_nil filter |
|
| 591 |
assert_include 'mine', filter[:values].map{|v| v[1]}
|
|
| 592 |
|
|
| 593 |
query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
|
|
| 594 |
result = query.issues |
|
| 595 |
assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
|
|
| 596 |
end |
|
| 597 |
|
|
| 598 |
def test_filter_watched_issues |
|
| 599 |
User.current = User.find(1) |
|
| 600 |
query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
|
|
| 601 |
result = find_issues_with_query(query) |
|
| 602 |
assert_not_nil result |
|
| 603 |
assert !result.empty? |
|
| 604 |
assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id) |
|
| 605 |
User.current = nil |
|
| 606 |
end |
|
| 607 |
|
|
| 608 |
def test_filter_unwatched_issues |
|
| 609 |
User.current = User.find(1) |
|
| 610 |
query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
|
|
| 611 |
result = find_issues_with_query(query) |
|
| 612 |
assert_not_nil result |
|
| 613 |
assert !result.empty? |
|
| 614 |
assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size) |
|
| 615 |
User.current = nil |
|
| 616 |
end |
|
| 617 |
|
|
| 618 |
def test_filter_on_project_custom_field |
|
| 619 |
field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
| 620 |
CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') |
|
| 621 |
CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') |
|
| 622 |
|
|
| 623 |
query = IssueQuery.new(:name => '_') |
|
| 624 |
filter_name = "project.cf_#{field.id}"
|
|
| 625 |
assert_include filter_name, query.available_filters.keys |
|
| 626 |
query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
|
|
| 627 |
assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort |
|
| 628 |
end |
|
| 629 |
|
|
| 630 |
def test_filter_on_author_custom_field |
|
| 631 |
field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
| 632 |
CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') |
|
| 633 |
|
|
| 634 |
query = IssueQuery.new(:name => '_') |
|
| 635 |
filter_name = "author.cf_#{field.id}"
|
|
| 636 |
assert_include filter_name, query.available_filters.keys |
|
| 637 |
query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
|
|
| 638 |
assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort |
|
| 639 |
end |
|
| 640 |
|
|
| 641 |
def test_filter_on_assigned_to_custom_field |
|
| 642 |
field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
| 643 |
CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') |
|
| 644 |
|
|
| 645 |
query = IssueQuery.new(:name => '_') |
|
| 646 |
filter_name = "assigned_to.cf_#{field.id}"
|
|
| 647 |
assert_include filter_name, query.available_filters.keys |
|
| 648 |
query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
|
|
| 649 |
assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort |
|
| 650 |
end |
|
| 651 |
|
|
| 652 |
def test_filter_on_fixed_version_custom_field |
|
| 653 |
field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
| 654 |
CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') |
|
| 655 |
|
|
| 656 |
query = IssueQuery.new(:name => '_') |
|
| 657 |
filter_name = "fixed_version.cf_#{field.id}"
|
|
| 658 |
assert_include filter_name, query.available_filters.keys |
|
| 659 |
query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
|
|
| 660 |
assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort |
|
| 661 |
end |
|
| 662 |
|
|
| 663 |
def test_filter_on_relations_with_a_specific_issue |
|
| 664 |
IssueRelation.delete_all |
|
| 665 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
| 666 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) |
|
| 667 |
|
|
| 668 |
query = IssueQuery.new(:name => '_') |
|
| 669 |
query.filters = {"relates" => {:operator => '=', :values => ['1']}}
|
|
| 670 |
assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort |
|
| 671 |
|
|
| 672 |
query = IssueQuery.new(:name => '_') |
|
| 673 |
query.filters = {"relates" => {:operator => '=', :values => ['2']}}
|
|
| 674 |
assert_equal [1], find_issues_with_query(query).map(&:id).sort |
|
| 675 |
end |
|
| 676 |
|
|
| 677 |
def test_filter_on_relations_with_any_issues_in_a_project |
|
| 678 |
IssueRelation.delete_all |
|
| 679 |
with_settings :cross_project_issue_relations => '1' do |
|
| 680 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) |
|
| 681 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first) |
|
| 682 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) |
|
| 683 |
end |
|
| 684 |
|
|
| 685 |
query = IssueQuery.new(:name => '_') |
|
| 686 |
query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
|
|
| 687 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
| 688 |
|
|
| 689 |
query = IssueQuery.new(:name => '_') |
|
| 690 |
query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
|
|
| 691 |
assert_equal [1], find_issues_with_query(query).map(&:id).sort |
|
| 692 |
|
|
| 693 |
query = IssueQuery.new(:name => '_') |
|
| 694 |
query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
|
|
| 695 |
assert_equal [], find_issues_with_query(query).map(&:id).sort |
|
| 696 |
end |
|
| 697 |
|
|
| 698 |
def test_filter_on_relations_with_any_issues_not_in_a_project |
|
| 699 |
IssueRelation.delete_all |
|
| 700 |
with_settings :cross_project_issue_relations => '1' do |
|
| 701 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) |
|
| 702 |
#IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first) |
|
| 703 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) |
|
| 704 |
end |
|
| 705 |
|
|
| 706 |
query = IssueQuery.new(:name => '_') |
|
| 707 |
query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
|
|
| 708 |
assert_equal [1], find_issues_with_query(query).map(&:id).sort |
|
| 709 |
end |
|
| 710 |
|
|
| 711 |
def test_filter_on_relations_with_no_issues_in_a_project |
|
| 712 |
IssueRelation.delete_all |
|
| 713 |
with_settings :cross_project_issue_relations => '1' do |
|
| 714 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) |
|
| 715 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first) |
|
| 716 |
IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3)) |
|
| 717 |
end |
|
| 718 |
|
|
| 719 |
query = IssueQuery.new(:name => '_') |
|
| 720 |
query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
|
|
| 721 |
ids = find_issues_with_query(query).map(&:id).sort |
|
| 722 |
assert_include 2, ids |
|
| 723 |
assert_not_include 1, ids |
|
| 724 |
assert_not_include 3, ids |
|
| 725 |
end |
|
| 726 |
|
|
| 727 |
def test_filter_on_relations_with_no_issues |
|
| 728 |
IssueRelation.delete_all |
|
| 729 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
| 730 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) |
|
| 731 |
|
|
| 732 |
query = IssueQuery.new(:name => '_') |
|
| 733 |
query.filters = {"relates" => {:operator => '!*', :values => ['']}}
|
|
| 734 |
ids = find_issues_with_query(query).map(&:id) |
|
| 735 |
assert_equal [], ids & [1, 2, 3] |
|
| 736 |
assert_include 4, ids |
|
| 737 |
end |
|
| 738 |
|
|
| 739 |
def test_filter_on_relations_with_any_issues |
|
| 740 |
IssueRelation.delete_all |
|
| 741 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
| 742 |
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) |
|
| 743 |
|
|
| 744 |
query = IssueQuery.new(:name => '_') |
|
| 745 |
query.filters = {"relates" => {:operator => '*', :values => ['']}}
|
|
| 746 |
assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort |
|
| 747 |
end |
|
| 748 |
|
|
| 749 |
def test_statement_should_be_nil_with_no_filters |
|
| 750 |
q = IssueQuery.new(:name => '_') |
|
| 751 |
q.filters = {}
|
|
| 752 |
|
|
| 753 |
assert q.valid? |
|
| 754 |
assert_nil q.statement |
|
| 755 |
end |
|
| 756 |
|
|
| 757 |
def test_default_columns |
|
| 758 |
q = IssueQuery.new |
|
| 759 |
assert q.columns.any? |
|
| 760 |
assert q.inline_columns.any? |
|
| 761 |
assert q.block_columns.empty? |
|
| 762 |
end |
|
| 763 |
|
|
| 764 |
def test_set_column_names |
|
| 765 |
q = IssueQuery.new |
|
| 766 |
q.column_names = ['tracker', :subject, '', 'unknonw_column'] |
|
| 767 |
assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
|
|
| 768 |
end |
|
| 769 |
|
|
| 770 |
def test_has_column_should_accept_a_column_name |
|
| 771 |
q = IssueQuery.new |
|
| 772 |
q.column_names = ['tracker', :subject] |
|
| 773 |
assert q.has_column?(:tracker) |
|
| 774 |
assert !q.has_column?(:category) |
|
| 775 |
end |
|
| 776 |
|
|
| 777 |
def test_has_column_should_accept_a_column |
|
| 778 |
q = IssueQuery.new |
|
| 779 |
q.column_names = ['tracker', :subject] |
|
| 780 |
|
|
| 781 |
tracker_column = q.available_columns.detect {|c| c.name==:tracker}
|
|
| 782 |
assert_kind_of QueryColumn, tracker_column |
|
| 783 |
category_column = q.available_columns.detect {|c| c.name==:category}
|
|
| 784 |
assert_kind_of QueryColumn, category_column |
|
| 785 |
|
|
| 786 |
assert q.has_column?(tracker_column) |
|
| 787 |
assert !q.has_column?(category_column) |
|
| 788 |
end |
|
| 789 |
|
|
| 790 |
def test_inline_and_block_columns |
|
| 791 |
q = IssueQuery.new |
|
| 792 |
q.column_names = ['subject', 'description', 'tracker'] |
|
| 793 |
|
|
| 794 |
assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name) |
|
| 795 |
assert_equal [:description], q.block_columns.map(&:name) |
|
| 796 |
end |
|
| 797 |
|
|
| 798 |
def test_custom_field_columns_should_be_inline |
|
| 799 |
q = IssueQuery.new |
|
| 800 |
columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
|
|
| 801 |
assert columns.any? |
|
| 802 |
assert_nil columns.detect {|column| !column.inline?}
|
|
| 803 |
end |
|
| 804 |
|
|
| 805 |
def test_query_should_preload_spent_hours |
|
| 806 |
q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours]) |
|
| 807 |
assert q.has_column?(:spent_hours) |
|
| 808 |
issues = q.issues |
|
| 809 |
assert_not_nil issues.first.instance_variable_get("@spent_hours")
|
|
| 810 |
end |
|
| 811 |
|
|
| 812 |
def test_groupable_columns_should_include_custom_fields |
|
| 813 |
q = IssueQuery.new |
|
| 814 |
column = q.groupable_columns.detect {|c| c.name == :cf_1}
|
|
| 815 |
assert_not_nil column |
|
| 816 |
assert_kind_of QueryCustomFieldColumn, column |
|
| 817 |
end |
|
| 818 |
|
|
| 819 |
def test_groupable_columns_should_not_include_multi_custom_fields |
|
| 820 |
field = CustomField.find(1) |
|
| 821 |
field.update_attribute :multiple, true |
|
| 822 |
|
|
| 823 |
q = IssueQuery.new |
|
Also available in: Unified diff