Revision 1298:4f746d8966dd .svn/pristine/45

View differences:

.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
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff