##// END OF EJS Templates
Merged r12896 (#16081)....
Jean-Philippe Lang -
r12671:d05cbf4df846
parent child
Show More
@@ -1,224 +1,228
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module QueriesHelper
20 module QueriesHelper
21 def filters_options_for_select(query)
21 def filters_options_for_select(query)
22 options_for_select(filters_options(query))
22 options_for_select(filters_options(query))
23 end
23 end
24
24
25 def filters_options(query)
25 def filters_options(query)
26 options = [[]]
26 options = [[]]
27 options += query.available_filters.map do |field, field_options|
27 options += query.available_filters.map do |field, field_options|
28 [field_options[:name], field]
28 [field_options[:name], field]
29 end
29 end
30 end
30 end
31
31
32 def query_filters_hidden_tags(query)
32 def query_filters_hidden_tags(query)
33 tags = ''.html_safe
33 tags = ''.html_safe
34 query.filters.each do |field, options|
34 query.filters.each do |field, options|
35 tags << hidden_field_tag("f[]", field, :id => nil)
35 tags << hidden_field_tag("f[]", field, :id => nil)
36 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
36 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
37 options[:values].each do |value|
37 options[:values].each do |value|
38 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
38 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
39 end
39 end
40 end
40 end
41 tags
41 tags
42 end
42 end
43
43
44 def query_columns_hidden_tags(query)
44 def query_columns_hidden_tags(query)
45 tags = ''.html_safe
45 tags = ''.html_safe
46 query.columns.each do |column|
46 query.columns.each do |column|
47 tags << hidden_field_tag("c[]", column.name, :id => nil)
47 tags << hidden_field_tag("c[]", column.name, :id => nil)
48 end
48 end
49 tags
49 tags
50 end
50 end
51
51
52 def query_hidden_tags(query)
52 def query_hidden_tags(query)
53 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
53 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
54 end
54 end
55
55
56 def available_block_columns_tags(query)
56 def available_block_columns_tags(query)
57 tags = ''.html_safe
57 tags = ''.html_safe
58 query.available_block_columns.each do |column|
58 query.available_block_columns.each do |column|
59 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
59 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
60 end
60 end
61 tags
61 tags
62 end
62 end
63
63
64 def query_available_inline_columns_options(query)
64 def query_available_inline_columns_options(query)
65 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
65 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
66 end
66 end
67
67
68 def query_selected_inline_columns_options(query)
68 def query_selected_inline_columns_options(query)
69 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
69 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
70 end
70 end
71
71
72 def render_query_columns_selection(query, options={})
72 def render_query_columns_selection(query, options={})
73 tag_name = (options[:name] || 'c') + '[]'
73 tag_name = (options[:name] || 'c') + '[]'
74 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
74 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
75 end
75 end
76
76
77 def column_header(column)
77 def column_header(column)
78 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
78 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
79 :default_order => column.default_order) :
79 :default_order => column.default_order) :
80 content_tag('th', h(column.caption))
80 content_tag('th', h(column.caption))
81 end
81 end
82
82
83 def column_content(column, issue)
83 def column_content(column, issue)
84 value = column.value(issue)
84 value = column.value(issue)
85 if value.is_a?(Array)
85 if value.is_a?(Array)
86 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
86 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
87 else
87 else
88 column_value(column, issue, value)
88 column_value(column, issue, value)
89 end
89 end
90 end
90 end
91
91
92 def column_value(column, issue, value)
92 def column_value(column, issue, value)
93 case value.class.name
93 case value.class.name
94 when 'String'
94 when 'String'
95 if column.name == :subject
95 if column.name == :subject
96 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
96 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
97 elsif column.name == :description
97 elsif column.name == :description
98 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
98 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
99 else
99 else
100 h(value)
100 h(value)
101 end
101 end
102 when 'Time'
102 when 'Time'
103 format_time(value)
103 format_time(value)
104 when 'Date'
104 when 'Date'
105 format_date(value)
105 format_date(value)
106 when 'Fixnum'
106 when 'Fixnum'
107 if column.name == :id
107 if column.name == :id
108 link_to value, issue_path(issue)
108 link_to value, issue_path(issue)
109 elsif column.name == :done_ratio
109 elsif column.name == :done_ratio
110 progress_bar(value, :width => '80px')
110 progress_bar(value, :width => '80px')
111 else
111 else
112 value.to_s
112 value.to_s
113 end
113 end
114 when 'Float'
114 when 'Float'
115 sprintf "%.2f", value
115 sprintf "%.2f", value
116 when 'User'
116 when 'User'
117 link_to_user value
117 link_to_user value
118 when 'Project'
118 when 'Project'
119 link_to_project value
119 link_to_project value
120 when 'Version'
120 when 'Version'
121 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
121 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
122 when 'TrueClass'
122 when 'TrueClass'
123 l(:general_text_Yes)
123 l(:general_text_Yes)
124 when 'FalseClass'
124 when 'FalseClass'
125 l(:general_text_No)
125 l(:general_text_No)
126 when 'Issue'
126 when 'Issue'
127 value.visible? ? link_to_issue(value) : "##{value.id}"
127 value.visible? ? link_to_issue(value) : "##{value.id}"
128 when 'IssueRelation'
128 when 'IssueRelation'
129 other = value.other_issue(issue)
129 other = value.other_issue(issue)
130 content_tag('span',
130 content_tag('span',
131 (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
131 (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
132 :class => value.css_classes_for(issue))
132 :class => value.css_classes_for(issue))
133 else
133 else
134 h(value)
134 h(value)
135 end
135 end
136 end
136 end
137
137
138 def csv_content(column, issue)
138 def csv_content(column, issue)
139 value = column.value(issue)
139 value = column.value(issue)
140 if value.is_a?(Array)
140 if value.is_a?(Array)
141 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
141 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
142 else
142 else
143 csv_value(column, issue, value)
143 csv_value(column, issue, value)
144 end
144 end
145 end
145 end
146
146
147 def csv_value(column, issue, value)
147 def csv_value(column, issue, value)
148 case value.class.name
148 case value.class.name
149 when 'Time'
149 when 'Time'
150 format_time(value)
150 format_time(value)
151 when 'Date'
151 when 'Date'
152 format_date(value)
152 format_date(value)
153 when 'Float'
153 when 'Float'
154 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
154 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
155 when 'IssueRelation'
155 when 'IssueRelation'
156 other = value.other_issue(issue)
156 other = value.other_issue(issue)
157 l(value.label_for(issue)) + " ##{other.id}"
157 l(value.label_for(issue)) + " ##{other.id}"
158 when 'TrueClass'
159 l(:general_text_Yes)
160 when 'FalseClass'
161 l(:general_text_No)
158 else
162 else
159 value.to_s
163 value.to_s
160 end
164 end
161 end
165 end
162
166
163 def query_to_csv(items, query, options={})
167 def query_to_csv(items, query, options={})
164 encoding = l(:general_csv_encoding)
168 encoding = l(:general_csv_encoding)
165 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
169 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
166 query.available_block_columns.each do |column|
170 query.available_block_columns.each do |column|
167 if options[column.name].present?
171 if options[column.name].present?
168 columns << column
172 columns << column
169 end
173 end
170 end
174 end
171
175
172 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
176 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
173 # csv header fields
177 # csv header fields
174 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
178 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
175 # csv lines
179 # csv lines
176 items.each do |item|
180 items.each do |item|
177 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
181 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
178 end
182 end
179 end
183 end
180 export
184 export
181 end
185 end
182
186
183 # Retrieve query from session or build a new query
187 # Retrieve query from session or build a new query
184 def retrieve_query
188 def retrieve_query
185 if !params[:query_id].blank?
189 if !params[:query_id].blank?
186 cond = "project_id IS NULL"
190 cond = "project_id IS NULL"
187 cond << " OR project_id = #{@project.id}" if @project
191 cond << " OR project_id = #{@project.id}" if @project
188 @query = IssueQuery.where(cond).find(params[:query_id])
192 @query = IssueQuery.where(cond).find(params[:query_id])
189 raise ::Unauthorized unless @query.visible?
193 raise ::Unauthorized unless @query.visible?
190 @query.project = @project
194 @query.project = @project
191 session[:query] = {:id => @query.id, :project_id => @query.project_id}
195 session[:query] = {:id => @query.id, :project_id => @query.project_id}
192 sort_clear
196 sort_clear
193 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
197 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
194 # Give it a name, required to be valid
198 # Give it a name, required to be valid
195 @query = IssueQuery.new(:name => "_")
199 @query = IssueQuery.new(:name => "_")
196 @query.project = @project
200 @query.project = @project
197 @query.build_from_params(params)
201 @query.build_from_params(params)
198 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
202 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
199 else
203 else
200 # retrieve from session
204 # retrieve from session
201 @query = nil
205 @query = nil
202 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
206 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
203 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
207 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
204 @query.project = @project
208 @query.project = @project
205 end
209 end
206 end
210 end
207
211
208 def retrieve_query_from_session
212 def retrieve_query_from_session
209 if session[:query]
213 if session[:query]
210 if session[:query][:id]
214 if session[:query][:id]
211 @query = IssueQuery.find_by_id(session[:query][:id])
215 @query = IssueQuery.find_by_id(session[:query][:id])
212 return unless @query
216 return unless @query
213 else
217 else
214 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
218 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
215 end
219 end
216 if session[:query].has_key?(:project_id)
220 if session[:query].has_key?(:project_id)
217 @query.project_id = session[:query][:project_id]
221 @query.project_id = session[:query][:project_id]
218 else
222 else
219 @query.project = @project
223 @query.project = @project
220 end
224 end
221 @query
225 @query
222 end
226 end
223 end
227 end
224 end
228 end
@@ -1,468 +1,476
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 #require 'shoulda'
18 #require 'shoulda'
19 ENV["RAILS_ENV"] = "test"
19 ENV["RAILS_ENV"] = "test"
20 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
21 require 'rails/test_help'
21 require 'rails/test_help'
22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
23
23
24 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
24 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
25 include ObjectHelpers
25 include ObjectHelpers
26
26
27 class ActiveSupport::TestCase
27 class ActiveSupport::TestCase
28 include ActionDispatch::TestProcess
28 include ActionDispatch::TestProcess
29
29
30 self.use_transactional_fixtures = true
30 self.use_transactional_fixtures = true
31 self.use_instantiated_fixtures = false
31 self.use_instantiated_fixtures = false
32
32
33 def log_user(login, password)
33 def log_user(login, password)
34 User.anonymous
34 User.anonymous
35 get "/login"
35 get "/login"
36 assert_equal nil, session[:user_id]
36 assert_equal nil, session[:user_id]
37 assert_response :success
37 assert_response :success
38 assert_template "account/login"
38 assert_template "account/login"
39 post "/login", :username => login, :password => password
39 post "/login", :username => login, :password => password
40 assert_equal login, User.find(session[:user_id]).login
40 assert_equal login, User.find(session[:user_id]).login
41 end
41 end
42
42
43 def uploaded_test_file(name, mime)
43 def uploaded_test_file(name, mime)
44 fixture_file_upload("files/#{name}", mime, true)
44 fixture_file_upload("files/#{name}", mime, true)
45 end
45 end
46
46
47 def credentials(user, password=nil)
47 def credentials(user, password=nil)
48 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
48 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
49 end
49 end
50
50
51 # Mock out a file
51 # Mock out a file
52 def self.mock_file
52 def self.mock_file
53 file = 'a_file.png'
53 file = 'a_file.png'
54 file.stubs(:size).returns(32)
54 file.stubs(:size).returns(32)
55 file.stubs(:original_filename).returns('a_file.png')
55 file.stubs(:original_filename).returns('a_file.png')
56 file.stubs(:content_type).returns('image/png')
56 file.stubs(:content_type).returns('image/png')
57 file.stubs(:read).returns(false)
57 file.stubs(:read).returns(false)
58 file
58 file
59 end
59 end
60
60
61 def mock_file
61 def mock_file
62 self.class.mock_file
62 self.class.mock_file
63 end
63 end
64
64
65 def mock_file_with_options(options={})
65 def mock_file_with_options(options={})
66 file = ''
66 file = ''
67 file.stubs(:size).returns(32)
67 file.stubs(:size).returns(32)
68 original_filename = options[:original_filename] || nil
68 original_filename = options[:original_filename] || nil
69 file.stubs(:original_filename).returns(original_filename)
69 file.stubs(:original_filename).returns(original_filename)
70 content_type = options[:content_type] || nil
70 content_type = options[:content_type] || nil
71 file.stubs(:content_type).returns(content_type)
71 file.stubs(:content_type).returns(content_type)
72 file.stubs(:read).returns(false)
72 file.stubs(:read).returns(false)
73 file
73 file
74 end
74 end
75
75
76 # Use a temporary directory for attachment related tests
76 # Use a temporary directory for attachment related tests
77 def set_tmp_attachments_directory
77 def set_tmp_attachments_directory
78 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
78 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
79 unless File.directory?("#{Rails.root}/tmp/test/attachments")
79 unless File.directory?("#{Rails.root}/tmp/test/attachments")
80 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
80 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
81 end
81 end
82 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
82 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
83 end
83 end
84
84
85 def set_fixtures_attachments_directory
85 def set_fixtures_attachments_directory
86 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
86 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
87 end
87 end
88
88
89 def with_settings(options, &block)
89 def with_settings(options, &block)
90 saved_settings = options.keys.inject({}) do |h, k|
90 saved_settings = options.keys.inject({}) do |h, k|
91 h[k] = case Setting[k]
91 h[k] = case Setting[k]
92 when Symbol, false, true, nil
92 when Symbol, false, true, nil
93 Setting[k]
93 Setting[k]
94 else
94 else
95 Setting[k].dup
95 Setting[k].dup
96 end
96 end
97 h
97 h
98 end
98 end
99 options.each {|k, v| Setting[k] = v}
99 options.each {|k, v| Setting[k] = v}
100 yield
100 yield
101 ensure
101 ensure
102 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
102 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
103 end
103 end
104
104
105 # Yields the block with user as the current user
105 # Yields the block with user as the current user
106 def with_current_user(user, &block)
106 def with_current_user(user, &block)
107 saved_user = User.current
107 saved_user = User.current
108 User.current = user
108 User.current = user
109 yield
109 yield
110 ensure
110 ensure
111 User.current = saved_user
111 User.current = saved_user
112 end
112 end
113
113
114 def with_locale(locale, &block)
115 saved_localed = ::I18n.locale
116 ::I18n.locale = locale
117 yield
118 ensure
119 ::I18n.locale = saved_localed
120 end
121
114 def change_user_password(login, new_password)
122 def change_user_password(login, new_password)
115 user = User.where(:login => login).first
123 user = User.where(:login => login).first
116 user.password, user.password_confirmation = new_password, new_password
124 user.password, user.password_confirmation = new_password, new_password
117 user.save!
125 user.save!
118 end
126 end
119
127
120 def self.ldap_configured?
128 def self.ldap_configured?
121 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
129 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
122 return @test_ldap.bind
130 return @test_ldap.bind
123 rescue Exception => e
131 rescue Exception => e
124 # LDAP is not listening
132 # LDAP is not listening
125 return nil
133 return nil
126 end
134 end
127
135
128 def self.convert_installed?
136 def self.convert_installed?
129 Redmine::Thumbnail.convert_available?
137 Redmine::Thumbnail.convert_available?
130 end
138 end
131
139
132 # Returns the path to the test +vendor+ repository
140 # Returns the path to the test +vendor+ repository
133 def self.repository_path(vendor)
141 def self.repository_path(vendor)
134 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
142 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
135 end
143 end
136
144
137 # Returns the url of the subversion test repository
145 # Returns the url of the subversion test repository
138 def self.subversion_repository_url
146 def self.subversion_repository_url
139 path = repository_path('subversion')
147 path = repository_path('subversion')
140 path = '/' + path unless path.starts_with?('/')
148 path = '/' + path unless path.starts_with?('/')
141 "file://#{path}"
149 "file://#{path}"
142 end
150 end
143
151
144 # Returns true if the +vendor+ test repository is configured
152 # Returns true if the +vendor+ test repository is configured
145 def self.repository_configured?(vendor)
153 def self.repository_configured?(vendor)
146 File.directory?(repository_path(vendor))
154 File.directory?(repository_path(vendor))
147 end
155 end
148
156
149 def repository_path_hash(arr)
157 def repository_path_hash(arr)
150 hs = {}
158 hs = {}
151 hs[:path] = arr.join("/")
159 hs[:path] = arr.join("/")
152 hs[:param] = arr.join("/")
160 hs[:param] = arr.join("/")
153 hs
161 hs
154 end
162 end
155
163
156 def assert_save(object)
164 def assert_save(object)
157 saved = object.save
165 saved = object.save
158 message = "#{object.class} could not be saved"
166 message = "#{object.class} could not be saved"
159 errors = object.errors.full_messages.map {|m| "- #{m}"}
167 errors = object.errors.full_messages.map {|m| "- #{m}"}
160 message << ":\n#{errors.join("\n")}" if errors.any?
168 message << ":\n#{errors.join("\n")}" if errors.any?
161 assert_equal true, saved, message
169 assert_equal true, saved, message
162 end
170 end
163
171
164 def assert_error_tag(options={})
172 def assert_error_tag(options={})
165 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
173 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
166 end
174 end
167
175
168 def assert_include(expected, s, message=nil)
176 def assert_include(expected, s, message=nil)
169 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
177 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
170 end
178 end
171
179
172 def assert_not_include(expected, s, message=nil)
180 def assert_not_include(expected, s, message=nil)
173 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
181 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
174 end
182 end
175
183
176 def assert_select_in(text, *args, &block)
184 def assert_select_in(text, *args, &block)
177 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
185 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
178 assert_select(d, *args, &block)
186 assert_select(d, *args, &block)
179 end
187 end
180
188
181 def assert_mail_body_match(expected, mail, message=nil)
189 def assert_mail_body_match(expected, mail, message=nil)
182 if expected.is_a?(String)
190 if expected.is_a?(String)
183 assert_include expected, mail_body(mail), message
191 assert_include expected, mail_body(mail), message
184 else
192 else
185 assert_match expected, mail_body(mail), message
193 assert_match expected, mail_body(mail), message
186 end
194 end
187 end
195 end
188
196
189 def assert_mail_body_no_match(expected, mail, message=nil)
197 def assert_mail_body_no_match(expected, mail, message=nil)
190 if expected.is_a?(String)
198 if expected.is_a?(String)
191 assert_not_include expected, mail_body(mail), message
199 assert_not_include expected, mail_body(mail), message
192 else
200 else
193 assert_no_match expected, mail_body(mail), message
201 assert_no_match expected, mail_body(mail), message
194 end
202 end
195 end
203 end
196
204
197 def mail_body(mail)
205 def mail_body(mail)
198 mail.parts.first.body.encoded
206 mail.parts.first.body.encoded
199 end
207 end
200 end
208 end
201
209
202 module Redmine
210 module Redmine
203 module ApiTest
211 module ApiTest
204 # Base class for API tests
212 # Base class for API tests
205 class Base < ActionDispatch::IntegrationTest
213 class Base < ActionDispatch::IntegrationTest
206 # Test that a request allows the three types of API authentication
214 # Test that a request allows the three types of API authentication
207 #
215 #
208 # * HTTP Basic with username and password
216 # * HTTP Basic with username and password
209 # * HTTP Basic with an api key for the username
217 # * HTTP Basic with an api key for the username
210 # * Key based with the key=X parameter
218 # * Key based with the key=X parameter
211 #
219 #
212 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
220 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
213 # @param [String] url the request url
221 # @param [String] url the request url
214 # @param [optional, Hash] parameters additional request parameters
222 # @param [optional, Hash] parameters additional request parameters
215 # @param [optional, Hash] options additional options
223 # @param [optional, Hash] options additional options
216 # @option options [Symbol] :success_code Successful response code (:success)
224 # @option options [Symbol] :success_code Successful response code (:success)
217 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
225 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
218 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
226 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
219 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
227 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
220 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
228 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
221 should_allow_key_based_auth(http_method, url, parameters, options)
229 should_allow_key_based_auth(http_method, url, parameters, options)
222 end
230 end
223
231
224 # Test that a request allows the username and password for HTTP BASIC
232 # Test that a request allows the username and password for HTTP BASIC
225 #
233 #
226 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
234 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
227 # @param [String] url the request url
235 # @param [String] url the request url
228 # @param [optional, Hash] parameters additional request parameters
236 # @param [optional, Hash] parameters additional request parameters
229 # @param [optional, Hash] options additional options
237 # @param [optional, Hash] options additional options
230 # @option options [Symbol] :success_code Successful response code (:success)
238 # @option options [Symbol] :success_code Successful response code (:success)
231 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
239 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
232 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
240 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
233 success_code = options[:success_code] || :success
241 success_code = options[:success_code] || :success
234 failure_code = options[:failure_code] || :unauthorized
242 failure_code = options[:failure_code] || :unauthorized
235
243
236 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
244 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
237 context "with a valid HTTP authentication" do
245 context "with a valid HTTP authentication" do
238 setup do
246 setup do
239 @user = User.generate! do |user|
247 @user = User.generate! do |user|
240 user.admin = true
248 user.admin = true
241 user.password = 'my_password'
249 user.password = 'my_password'
242 end
250 end
243 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
251 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
244 end
252 end
245
253
246 should_respond_with success_code
254 should_respond_with success_code
247 should_respond_with_content_type_based_on_url(url)
255 should_respond_with_content_type_based_on_url(url)
248 should "login as the user" do
256 should "login as the user" do
249 assert_equal @user, User.current
257 assert_equal @user, User.current
250 end
258 end
251 end
259 end
252
260
253 context "with an invalid HTTP authentication" do
261 context "with an invalid HTTP authentication" do
254 setup do
262 setup do
255 @user = User.generate!
263 @user = User.generate!
256 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
264 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
257 end
265 end
258
266
259 should_respond_with failure_code
267 should_respond_with failure_code
260 should_respond_with_content_type_based_on_url(url)
268 should_respond_with_content_type_based_on_url(url)
261 should "not login as the user" do
269 should "not login as the user" do
262 assert_equal User.anonymous, User.current
270 assert_equal User.anonymous, User.current
263 end
271 end
264 end
272 end
265
273
266 context "without credentials" do
274 context "without credentials" do
267 setup do
275 setup do
268 send(http_method, url, parameters)
276 send(http_method, url, parameters)
269 end
277 end
270
278
271 should_respond_with failure_code
279 should_respond_with failure_code
272 should_respond_with_content_type_based_on_url(url)
280 should_respond_with_content_type_based_on_url(url)
273 should "include_www_authenticate_header" do
281 should "include_www_authenticate_header" do
274 assert @controller.response.headers.has_key?('WWW-Authenticate')
282 assert @controller.response.headers.has_key?('WWW-Authenticate')
275 end
283 end
276 end
284 end
277 end
285 end
278 end
286 end
279
287
280 # Test that a request allows the API key with HTTP BASIC
288 # Test that a request allows the API key with HTTP BASIC
281 #
289 #
282 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
290 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
283 # @param [String] url the request url
291 # @param [String] url the request url
284 # @param [optional, Hash] parameters additional request parameters
292 # @param [optional, Hash] parameters additional request parameters
285 # @param [optional, Hash] options additional options
293 # @param [optional, Hash] options additional options
286 # @option options [Symbol] :success_code Successful response code (:success)
294 # @option options [Symbol] :success_code Successful response code (:success)
287 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
295 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
288 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
296 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
289 success_code = options[:success_code] || :success
297 success_code = options[:success_code] || :success
290 failure_code = options[:failure_code] || :unauthorized
298 failure_code = options[:failure_code] || :unauthorized
291
299
292 context "should allow http basic auth with a key for #{http_method} #{url}" do
300 context "should allow http basic auth with a key for #{http_method} #{url}" do
293 context "with a valid HTTP authentication using the API token" do
301 context "with a valid HTTP authentication using the API token" do
294 setup do
302 setup do
295 @user = User.generate! do |user|
303 @user = User.generate! do |user|
296 user.admin = true
304 user.admin = true
297 end
305 end
298 @token = Token.create!(:user => @user, :action => 'api')
306 @token = Token.create!(:user => @user, :action => 'api')
299 send(http_method, url, parameters, credentials(@token.value, 'X'))
307 send(http_method, url, parameters, credentials(@token.value, 'X'))
300 end
308 end
301 should_respond_with success_code
309 should_respond_with success_code
302 should_respond_with_content_type_based_on_url(url)
310 should_respond_with_content_type_based_on_url(url)
303 should_be_a_valid_response_string_based_on_url(url)
311 should_be_a_valid_response_string_based_on_url(url)
304 should "login as the user" do
312 should "login as the user" do
305 assert_equal @user, User.current
313 assert_equal @user, User.current
306 end
314 end
307 end
315 end
308
316
309 context "with an invalid HTTP authentication" do
317 context "with an invalid HTTP authentication" do
310 setup do
318 setup do
311 @user = User.generate!
319 @user = User.generate!
312 @token = Token.create!(:user => @user, :action => 'feeds')
320 @token = Token.create!(:user => @user, :action => 'feeds')
313 send(http_method, url, parameters, credentials(@token.value, 'X'))
321 send(http_method, url, parameters, credentials(@token.value, 'X'))
314 end
322 end
315 should_respond_with failure_code
323 should_respond_with failure_code
316 should_respond_with_content_type_based_on_url(url)
324 should_respond_with_content_type_based_on_url(url)
317 should "not login as the user" do
325 should "not login as the user" do
318 assert_equal User.anonymous, User.current
326 assert_equal User.anonymous, User.current
319 end
327 end
320 end
328 end
321 end
329 end
322 end
330 end
323
331
324 # Test that a request allows full key authentication
332 # Test that a request allows full key authentication
325 #
333 #
326 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
334 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
327 # @param [String] url the request url, without the key=ZXY parameter
335 # @param [String] url the request url, without the key=ZXY parameter
328 # @param [optional, Hash] parameters additional request parameters
336 # @param [optional, Hash] parameters additional request parameters
329 # @param [optional, Hash] options additional options
337 # @param [optional, Hash] options additional options
330 # @option options [Symbol] :success_code Successful response code (:success)
338 # @option options [Symbol] :success_code Successful response code (:success)
331 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
339 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
332 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
340 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
333 success_code = options[:success_code] || :success
341 success_code = options[:success_code] || :success
334 failure_code = options[:failure_code] || :unauthorized
342 failure_code = options[:failure_code] || :unauthorized
335
343
336 context "should allow key based auth using key=X for #{http_method} #{url}" do
344 context "should allow key based auth using key=X for #{http_method} #{url}" do
337 context "with a valid api token" do
345 context "with a valid api token" do
338 setup do
346 setup do
339 @user = User.generate! do |user|
347 @user = User.generate! do |user|
340 user.admin = true
348 user.admin = true
341 end
349 end
342 @token = Token.create!(:user => @user, :action => 'api')
350 @token = Token.create!(:user => @user, :action => 'api')
343 # Simple url parse to add on ?key= or &key=
351 # Simple url parse to add on ?key= or &key=
344 request_url = if url.match(/\?/)
352 request_url = if url.match(/\?/)
345 url + "&key=#{@token.value}"
353 url + "&key=#{@token.value}"
346 else
354 else
347 url + "?key=#{@token.value}"
355 url + "?key=#{@token.value}"
348 end
356 end
349 send(http_method, request_url, parameters)
357 send(http_method, request_url, parameters)
350 end
358 end
351 should_respond_with success_code
359 should_respond_with success_code
352 should_respond_with_content_type_based_on_url(url)
360 should_respond_with_content_type_based_on_url(url)
353 should_be_a_valid_response_string_based_on_url(url)
361 should_be_a_valid_response_string_based_on_url(url)
354 should "login as the user" do
362 should "login as the user" do
355 assert_equal @user, User.current
363 assert_equal @user, User.current
356 end
364 end
357 end
365 end
358
366
359 context "with an invalid api token" do
367 context "with an invalid api token" do
360 setup do
368 setup do
361 @user = User.generate! do |user|
369 @user = User.generate! do |user|
362 user.admin = true
370 user.admin = true
363 end
371 end
364 @token = Token.create!(:user => @user, :action => 'feeds')
372 @token = Token.create!(:user => @user, :action => 'feeds')
365 # Simple url parse to add on ?key= or &key=
373 # Simple url parse to add on ?key= or &key=
366 request_url = if url.match(/\?/)
374 request_url = if url.match(/\?/)
367 url + "&key=#{@token.value}"
375 url + "&key=#{@token.value}"
368 else
376 else
369 url + "?key=#{@token.value}"
377 url + "?key=#{@token.value}"
370 end
378 end
371 send(http_method, request_url, parameters)
379 send(http_method, request_url, parameters)
372 end
380 end
373 should_respond_with failure_code
381 should_respond_with failure_code
374 should_respond_with_content_type_based_on_url(url)
382 should_respond_with_content_type_based_on_url(url)
375 should "not login as the user" do
383 should "not login as the user" do
376 assert_equal User.anonymous, User.current
384 assert_equal User.anonymous, User.current
377 end
385 end
378 end
386 end
379 end
387 end
380
388
381 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
389 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
382 setup do
390 setup do
383 @user = User.generate! do |user|
391 @user = User.generate! do |user|
384 user.admin = true
392 user.admin = true
385 end
393 end
386 @token = Token.create!(:user => @user, :action => 'api')
394 @token = Token.create!(:user => @user, :action => 'api')
387 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
395 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
388 end
396 end
389 should_respond_with success_code
397 should_respond_with success_code
390 should_respond_with_content_type_based_on_url(url)
398 should_respond_with_content_type_based_on_url(url)
391 should_be_a_valid_response_string_based_on_url(url)
399 should_be_a_valid_response_string_based_on_url(url)
392 should "login as the user" do
400 should "login as the user" do
393 assert_equal @user, User.current
401 assert_equal @user, User.current
394 end
402 end
395 end
403 end
396 end
404 end
397
405
398 # Uses should_respond_with_content_type based on what's in the url:
406 # Uses should_respond_with_content_type based on what's in the url:
399 #
407 #
400 # '/project/issues.xml' => should_respond_with_content_type :xml
408 # '/project/issues.xml' => should_respond_with_content_type :xml
401 # '/project/issues.json' => should_respond_with_content_type :json
409 # '/project/issues.json' => should_respond_with_content_type :json
402 #
410 #
403 # @param [String] url Request
411 # @param [String] url Request
404 def self.should_respond_with_content_type_based_on_url(url)
412 def self.should_respond_with_content_type_based_on_url(url)
405 case
413 case
406 when url.match(/xml/i)
414 when url.match(/xml/i)
407 should "respond with XML" do
415 should "respond with XML" do
408 assert_equal 'application/xml', @response.content_type
416 assert_equal 'application/xml', @response.content_type
409 end
417 end
410 when url.match(/json/i)
418 when url.match(/json/i)
411 should "respond with JSON" do
419 should "respond with JSON" do
412 assert_equal 'application/json', @response.content_type
420 assert_equal 'application/json', @response.content_type
413 end
421 end
414 else
422 else
415 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
423 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
416 end
424 end
417 end
425 end
418
426
419 # Uses the url to assert which format the response should be in
427 # Uses the url to assert which format the response should be in
420 #
428 #
421 # '/project/issues.xml' => should_be_a_valid_xml_string
429 # '/project/issues.xml' => should_be_a_valid_xml_string
422 # '/project/issues.json' => should_be_a_valid_json_string
430 # '/project/issues.json' => should_be_a_valid_json_string
423 #
431 #
424 # @param [String] url Request
432 # @param [String] url Request
425 def self.should_be_a_valid_response_string_based_on_url(url)
433 def self.should_be_a_valid_response_string_based_on_url(url)
426 case
434 case
427 when url.match(/xml/i)
435 when url.match(/xml/i)
428 should_be_a_valid_xml_string
436 should_be_a_valid_xml_string
429 when url.match(/json/i)
437 when url.match(/json/i)
430 should_be_a_valid_json_string
438 should_be_a_valid_json_string
431 else
439 else
432 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
440 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
433 end
441 end
434 end
442 end
435
443
436 # Checks that the response is a valid JSON string
444 # Checks that the response is a valid JSON string
437 def self.should_be_a_valid_json_string
445 def self.should_be_a_valid_json_string
438 should "be a valid JSON string (or empty)" do
446 should "be a valid JSON string (or empty)" do
439 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
447 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
440 end
448 end
441 end
449 end
442
450
443 # Checks that the response is a valid XML string
451 # Checks that the response is a valid XML string
444 def self.should_be_a_valid_xml_string
452 def self.should_be_a_valid_xml_string
445 should "be a valid XML string" do
453 should "be a valid XML string" do
446 assert REXML::Document.new(response.body)
454 assert REXML::Document.new(response.body)
447 end
455 end
448 end
456 end
449
457
450 def self.should_respond_with(status)
458 def self.should_respond_with(status)
451 should "respond with #{status}" do
459 should "respond with #{status}" do
452 assert_response status
460 assert_response status
453 end
461 end
454 end
462 end
455 end
463 end
456 end
464 end
457 end
465 end
458
466
459 # URL helpers do not work with config.threadsafe!
467 # URL helpers do not work with config.threadsafe!
460 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
468 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
461 ActionView::TestCase::TestController.instance_eval do
469 ActionView::TestCase::TestController.instance_eval do
462 helper Rails.application.routes.url_helpers
470 helper Rails.application.routes.url_helpers
463 end
471 end
464 ActionView::TestCase::TestController.class_eval do
472 ActionView::TestCase::TestController.class_eval do
465 def _routes
473 def _routes
466 Rails.application.routes
474 Rails.application.routes
467 end
475 end
468 end
476 end
@@ -1,39 +1,53
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class QueriesHelperTest < ActionView::TestCase
20 class QueriesHelperTest < ActionView::TestCase
21 include QueriesHelper
21 include QueriesHelper
22 include Redmine::I18n
22 include Redmine::I18n
23
23
24 fixtures :projects, :enabled_modules, :users, :members,
24 fixtures :projects, :enabled_modules, :users, :members,
25 :member_roles, :roles, :trackers, :issue_statuses,
25 :member_roles, :roles, :trackers, :issue_statuses,
26 :issue_categories, :enumerations, :issues,
26 :issue_categories, :enumerations, :issues,
27 :watchers, :custom_fields, :custom_values, :versions,
27 :watchers, :custom_fields, :custom_values, :versions,
28 :queries,
28 :queries,
29 :projects_trackers,
29 :projects_trackers,
30 :custom_fields_trackers
30 :custom_fields_trackers
31
31
32 def test_filters_options_has_empty_item
32 def test_filters_options_has_empty_item
33 query = IssueQuery.new
33 query = IssueQuery.new
34 filter_count = query.available_filters.size
34 filter_count = query.available_filters.size
35 fo = filters_options(query)
35 fo = filters_options(query)
36 assert_equal filter_count + 1, fo.size
36 assert_equal filter_count + 1, fo.size
37 assert_equal [], fo[0]
37 assert_equal [], fo[0]
38 end
38 end
39
40 def test_query_to_csv_should_translate_boolean_custom_field_values
41 f = IssueCustomField.generate!(:field_format => 'bool', :name => 'Boolean', :is_for_all => true, :trackers => Tracker.all)
42 issues = [
43 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '0'}),
44 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '1'})
45 ]
46
47 with_locale 'fr' do
48 csv = query_to_csv(issues, IssueQuery.new, :columns => 'all')
49 assert_include "Oui", csv
50 assert_include "Non", csv
51 end
52 end
39 end
53 end
General Comments 0
You need to be logged in to leave comments. Login now