##// END OF EJS Templates
Fixed: Export CSV - Custom field true/false not using translation (#16081)....
Jean-Philippe Lang -
r12621:1114bf5e99e4
parent child
Show More
@@ -1,198 +1,202
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), :id => nil) + " #{column.caption}", :class => 'inline')
59 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{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 column.name
93 case column.name
94 when :id
94 when :id
95 link_to value, issue_path(issue)
95 link_to value, issue_path(issue)
96 when :subject
96 when :subject
97 link_to value, issue_path(issue)
97 link_to value, issue_path(issue)
98 when :description
98 when :description
99 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
99 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
100 when :done_ratio
100 when :done_ratio
101 progress_bar(value, :width => '80px')
101 progress_bar(value, :width => '80px')
102 when :relations
102 when :relations
103 other = value.other_issue(issue)
103 other = value.other_issue(issue)
104 content_tag('span',
104 content_tag('span',
105 (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
105 (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
106 :class => value.css_classes_for(issue))
106 :class => value.css_classes_for(issue))
107 else
107 else
108 format_object(value)
108 format_object(value)
109 end
109 end
110 end
110 end
111
111
112 def csv_content(column, issue)
112 def csv_content(column, issue)
113 value = column.value(issue)
113 value = column.value(issue)
114 if value.is_a?(Array)
114 if value.is_a?(Array)
115 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
115 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
116 else
116 else
117 csv_value(column, issue, value)
117 csv_value(column, issue, value)
118 end
118 end
119 end
119 end
120
120
121 def csv_value(column, issue, value)
121 def csv_value(column, issue, value)
122 case value.class.name
122 case value.class.name
123 when 'Time'
123 when 'Time'
124 format_time(value)
124 format_time(value)
125 when 'Date'
125 when 'Date'
126 format_date(value)
126 format_date(value)
127 when 'Float'
127 when 'Float'
128 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
128 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
129 when 'IssueRelation'
129 when 'IssueRelation'
130 other = value.other_issue(issue)
130 other = value.other_issue(issue)
131 l(value.label_for(issue)) + " ##{other.id}"
131 l(value.label_for(issue)) + " ##{other.id}"
132 when 'TrueClass'
133 l(:general_text_Yes)
134 when 'FalseClass'
135 l(:general_text_No)
132 else
136 else
133 value.to_s
137 value.to_s
134 end
138 end
135 end
139 end
136
140
137 def query_to_csv(items, query, options={})
141 def query_to_csv(items, query, options={})
138 encoding = l(:general_csv_encoding)
142 encoding = l(:general_csv_encoding)
139 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
143 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
140 query.available_block_columns.each do |column|
144 query.available_block_columns.each do |column|
141 if options[column.name].present?
145 if options[column.name].present?
142 columns << column
146 columns << column
143 end
147 end
144 end
148 end
145
149
146 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
150 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
147 # csv header fields
151 # csv header fields
148 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
152 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
149 # csv lines
153 # csv lines
150 items.each do |item|
154 items.each do |item|
151 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
155 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
152 end
156 end
153 end
157 end
154 export
158 export
155 end
159 end
156
160
157 # Retrieve query from session or build a new query
161 # Retrieve query from session or build a new query
158 def retrieve_query
162 def retrieve_query
159 if !params[:query_id].blank?
163 if !params[:query_id].blank?
160 cond = "project_id IS NULL"
164 cond = "project_id IS NULL"
161 cond << " OR project_id = #{@project.id}" if @project
165 cond << " OR project_id = #{@project.id}" if @project
162 @query = IssueQuery.where(cond).find(params[:query_id])
166 @query = IssueQuery.where(cond).find(params[:query_id])
163 raise ::Unauthorized unless @query.visible?
167 raise ::Unauthorized unless @query.visible?
164 @query.project = @project
168 @query.project = @project
165 session[:query] = {:id => @query.id, :project_id => @query.project_id}
169 session[:query] = {:id => @query.id, :project_id => @query.project_id}
166 sort_clear
170 sort_clear
167 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
171 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
168 # Give it a name, required to be valid
172 # Give it a name, required to be valid
169 @query = IssueQuery.new(:name => "_")
173 @query = IssueQuery.new(:name => "_")
170 @query.project = @project
174 @query.project = @project
171 @query.build_from_params(params)
175 @query.build_from_params(params)
172 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
176 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
173 else
177 else
174 # retrieve from session
178 # retrieve from session
175 @query = nil
179 @query = nil
176 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
180 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
177 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
181 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
178 @query.project = @project
182 @query.project = @project
179 end
183 end
180 end
184 end
181
185
182 def retrieve_query_from_session
186 def retrieve_query_from_session
183 if session[:query]
187 if session[:query]
184 if session[:query][:id]
188 if session[:query][:id]
185 @query = IssueQuery.find_by_id(session[:query][:id])
189 @query = IssueQuery.find_by_id(session[:query][:id])
186 return unless @query
190 return unless @query
187 else
191 else
188 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
192 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
189 end
193 end
190 if session[:query].has_key?(:project_id)
194 if session[:query].has_key?(:project_id)
191 @query.project_id = session[:query][:project_id]
195 @query.project_id = session[:query][:project_id]
192 else
196 else
193 @query.project = @project
197 @query.project = @project
194 end
198 end
195 @query
199 @query
196 end
200 end
197 end
201 end
198 end
202 end
@@ -1,486 +1,494
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 require 'awesome_nested_set/version'
27 require 'awesome_nested_set/version'
28
28
29 class ActiveSupport::TestCase
29 class ActiveSupport::TestCase
30 include ActionDispatch::TestProcess
30 include ActionDispatch::TestProcess
31
31
32 self.use_transactional_fixtures = true
32 self.use_transactional_fixtures = true
33 self.use_instantiated_fixtures = false
33 self.use_instantiated_fixtures = false
34
34
35 ESCAPED_CANT = 'can&#x27;t'
35 ESCAPED_CANT = 'can&#x27;t'
36 ESCAPED_UCANT = 'Can&#x27;t'
36 ESCAPED_UCANT = 'Can&#x27;t'
37 # Rails 4.0.2
37 # Rails 4.0.2
38 #ESCAPED_CANT = 'can&#39;t'
38 #ESCAPED_CANT = 'can&#39;t'
39 #ESCAPED_UCANT = 'Can&#39;t'
39 #ESCAPED_UCANT = 'Can&#39;t'
40
40
41 def log_user(login, password)
41 def log_user(login, password)
42 User.anonymous
42 User.anonymous
43 get "/login"
43 get "/login"
44 assert_equal nil, session[:user_id]
44 assert_equal nil, session[:user_id]
45 assert_response :success
45 assert_response :success
46 assert_template "account/login"
46 assert_template "account/login"
47 post "/login", :username => login, :password => password
47 post "/login", :username => login, :password => password
48 assert_equal login, User.find(session[:user_id]).login
48 assert_equal login, User.find(session[:user_id]).login
49 end
49 end
50
50
51 def uploaded_test_file(name, mime)
51 def uploaded_test_file(name, mime)
52 fixture_file_upload("files/#{name}", mime, true)
52 fixture_file_upload("files/#{name}", mime, true)
53 end
53 end
54
54
55 def credentials(user, password=nil)
55 def credentials(user, password=nil)
56 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
56 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
57 end
57 end
58
58
59 # Mock out a file
59 # Mock out a file
60 def self.mock_file
60 def self.mock_file
61 file = 'a_file.png'
61 file = 'a_file.png'
62 file.stubs(:size).returns(32)
62 file.stubs(:size).returns(32)
63 file.stubs(:original_filename).returns('a_file.png')
63 file.stubs(:original_filename).returns('a_file.png')
64 file.stubs(:content_type).returns('image/png')
64 file.stubs(:content_type).returns('image/png')
65 file.stubs(:read).returns(false)
65 file.stubs(:read).returns(false)
66 file
66 file
67 end
67 end
68
68
69 def mock_file
69 def mock_file
70 self.class.mock_file
70 self.class.mock_file
71 end
71 end
72
72
73 def mock_file_with_options(options={})
73 def mock_file_with_options(options={})
74 file = ''
74 file = ''
75 file.stubs(:size).returns(32)
75 file.stubs(:size).returns(32)
76 original_filename = options[:original_filename] || nil
76 original_filename = options[:original_filename] || nil
77 file.stubs(:original_filename).returns(original_filename)
77 file.stubs(:original_filename).returns(original_filename)
78 content_type = options[:content_type] || nil
78 content_type = options[:content_type] || nil
79 file.stubs(:content_type).returns(content_type)
79 file.stubs(:content_type).returns(content_type)
80 file.stubs(:read).returns(false)
80 file.stubs(:read).returns(false)
81 file
81 file
82 end
82 end
83
83
84 # Use a temporary directory for attachment related tests
84 # Use a temporary directory for attachment related tests
85 def set_tmp_attachments_directory
85 def set_tmp_attachments_directory
86 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
86 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
87 unless File.directory?("#{Rails.root}/tmp/test/attachments")
87 unless File.directory?("#{Rails.root}/tmp/test/attachments")
88 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
88 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
89 end
89 end
90 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
90 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
91 end
91 end
92
92
93 def set_fixtures_attachments_directory
93 def set_fixtures_attachments_directory
94 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
94 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
95 end
95 end
96
96
97 def with_settings(options, &block)
97 def with_settings(options, &block)
98 saved_settings = options.keys.inject({}) do |h, k|
98 saved_settings = options.keys.inject({}) do |h, k|
99 h[k] = case Setting[k]
99 h[k] = case Setting[k]
100 when Symbol, false, true, nil
100 when Symbol, false, true, nil
101 Setting[k]
101 Setting[k]
102 else
102 else
103 Setting[k].dup
103 Setting[k].dup
104 end
104 end
105 h
105 h
106 end
106 end
107 options.each {|k, v| Setting[k] = v}
107 options.each {|k, v| Setting[k] = v}
108 yield
108 yield
109 ensure
109 ensure
110 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
110 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
111 end
111 end
112
112
113 # Yields the block with user as the current user
113 # Yields the block with user as the current user
114 def with_current_user(user, &block)
114 def with_current_user(user, &block)
115 saved_user = User.current
115 saved_user = User.current
116 User.current = user
116 User.current = user
117 yield
117 yield
118 ensure
118 ensure
119 User.current = saved_user
119 User.current = saved_user
120 end
120 end
121
121
122 def with_locale(locale, &block)
123 saved_localed = ::I18n.locale
124 ::I18n.locale = locale
125 yield
126 ensure
127 ::I18n.locale = saved_localed
128 end
129
122 def change_user_password(login, new_password)
130 def change_user_password(login, new_password)
123 user = User.where(:login => login).first
131 user = User.where(:login => login).first
124 user.password, user.password_confirmation = new_password, new_password
132 user.password, user.password_confirmation = new_password, new_password
125 user.save!
133 user.save!
126 end
134 end
127
135
128 def self.ldap_configured?
136 def self.ldap_configured?
129 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
137 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
130 return @test_ldap.bind
138 return @test_ldap.bind
131 rescue Exception => e
139 rescue Exception => e
132 # LDAP is not listening
140 # LDAP is not listening
133 return nil
141 return nil
134 end
142 end
135
143
136 def self.convert_installed?
144 def self.convert_installed?
137 Redmine::Thumbnail.convert_available?
145 Redmine::Thumbnail.convert_available?
138 end
146 end
139
147
140 # Returns the path to the test +vendor+ repository
148 # Returns the path to the test +vendor+ repository
141 def self.repository_path(vendor)
149 def self.repository_path(vendor)
142 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
150 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
143 end
151 end
144
152
145 # Returns the url of the subversion test repository
153 # Returns the url of the subversion test repository
146 def self.subversion_repository_url
154 def self.subversion_repository_url
147 path = repository_path('subversion')
155 path = repository_path('subversion')
148 path = '/' + path unless path.starts_with?('/')
156 path = '/' + path unless path.starts_with?('/')
149 "file://#{path}"
157 "file://#{path}"
150 end
158 end
151
159
152 # Returns true if the +vendor+ test repository is configured
160 # Returns true if the +vendor+ test repository is configured
153 def self.repository_configured?(vendor)
161 def self.repository_configured?(vendor)
154 File.directory?(repository_path(vendor))
162 File.directory?(repository_path(vendor))
155 end
163 end
156
164
157 def repository_path_hash(arr)
165 def repository_path_hash(arr)
158 hs = {}
166 hs = {}
159 hs[:path] = arr.join("/")
167 hs[:path] = arr.join("/")
160 hs[:param] = arr.join("/")
168 hs[:param] = arr.join("/")
161 hs
169 hs
162 end
170 end
163
171
164 def assert_save(object)
172 def assert_save(object)
165 saved = object.save
173 saved = object.save
166 message = "#{object.class} could not be saved"
174 message = "#{object.class} could not be saved"
167 errors = object.errors.full_messages.map {|m| "- #{m}"}
175 errors = object.errors.full_messages.map {|m| "- #{m}"}
168 message << ":\n#{errors.join("\n")}" if errors.any?
176 message << ":\n#{errors.join("\n")}" if errors.any?
169 assert_equal true, saved, message
177 assert_equal true, saved, message
170 end
178 end
171
179
172 def assert_error_tag(options={})
180 def assert_error_tag(options={})
173 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
181 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
174 end
182 end
175
183
176 def assert_include(expected, s, message=nil)
184 def assert_include(expected, s, message=nil)
177 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
185 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
178 end
186 end
179
187
180 def assert_not_include(expected, s, message=nil)
188 def assert_not_include(expected, s, message=nil)
181 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
189 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
182 end
190 end
183
191
184 def assert_select_in(text, *args, &block)
192 def assert_select_in(text, *args, &block)
185 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
193 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
186 assert_select(d, *args, &block)
194 assert_select(d, *args, &block)
187 end
195 end
188
196
189 def assert_mail_body_match(expected, mail, message=nil)
197 def assert_mail_body_match(expected, mail, message=nil)
190 if expected.is_a?(String)
198 if expected.is_a?(String)
191 assert_include expected, mail_body(mail), message
199 assert_include expected, mail_body(mail), message
192 else
200 else
193 assert_match expected, mail_body(mail), message
201 assert_match expected, mail_body(mail), message
194 end
202 end
195 end
203 end
196
204
197 def assert_mail_body_no_match(expected, mail, message=nil)
205 def assert_mail_body_no_match(expected, mail, message=nil)
198 if expected.is_a?(String)
206 if expected.is_a?(String)
199 assert_not_include expected, mail_body(mail), message
207 assert_not_include expected, mail_body(mail), message
200 else
208 else
201 assert_no_match expected, mail_body(mail), message
209 assert_no_match expected, mail_body(mail), message
202 end
210 end
203 end
211 end
204
212
205 def mail_body(mail)
213 def mail_body(mail)
206 mail.parts.first.body.encoded
214 mail.parts.first.body.encoded
207 end
215 end
208
216
209 # awesome_nested_set new node lft and rgt value changed this refactor revision.
217 # awesome_nested_set new node lft and rgt value changed this refactor revision.
210 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb938e40200cd90714dc69247ef017c61
218 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb938e40200cd90714dc69247ef017c61
211 # The reason of behavior change is that "self.class.base_class.unscoped" was added to this line.
219 # The reason of behavior change is that "self.class.base_class.unscoped" was added to this line.
212 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb9#diff-f61b59a5e6319024e211b0ffdd0e4ef1R273
220 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb9#diff-f61b59a5e6319024e211b0ffdd0e4ef1R273
213 # It seems correct behavior because of this line comment.
221 # It seems correct behavior because of this line comment.
214 # https://github.com/collectiveidea/awesome_nested_set/blame/199fca9bb9/lib/awesome_nested_set/model.rb#L278
222 # https://github.com/collectiveidea/awesome_nested_set/blame/199fca9bb9/lib/awesome_nested_set/model.rb#L278
215 def new_issue_lft
223 def new_issue_lft
216 ::AwesomeNestedSet::VERSION > "2.1.6" ? Issue.maximum(:rgt) + 1 : 1
224 ::AwesomeNestedSet::VERSION > "2.1.6" ? Issue.maximum(:rgt) + 1 : 1
217 end
225 end
218 end
226 end
219
227
220 module Redmine
228 module Redmine
221 module ApiTest
229 module ApiTest
222 # Base class for API tests
230 # Base class for API tests
223 class Base < ActionDispatch::IntegrationTest
231 class Base < ActionDispatch::IntegrationTest
224 # Test that a request allows the three types of API authentication
232 # Test that a request allows the three types of API authentication
225 #
233 #
226 # * HTTP Basic with username and password
234 # * HTTP Basic with username and password
227 # * HTTP Basic with an api key for the username
235 # * HTTP Basic with an api key for the username
228 # * Key based with the key=X parameter
236 # * Key based with the key=X parameter
229 #
237 #
230 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
238 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
231 # @param [String] url the request url
239 # @param [String] url the request url
232 # @param [optional, Hash] parameters additional request parameters
240 # @param [optional, Hash] parameters additional request parameters
233 # @param [optional, Hash] options additional options
241 # @param [optional, Hash] options additional options
234 # @option options [Symbol] :success_code Successful response code (:success)
242 # @option options [Symbol] :success_code Successful response code (:success)
235 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
243 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
236 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
244 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
237 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
245 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
238 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
246 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
239 should_allow_key_based_auth(http_method, url, parameters, options)
247 should_allow_key_based_auth(http_method, url, parameters, options)
240 end
248 end
241
249
242 # Test that a request allows the username and password for HTTP BASIC
250 # Test that a request allows the username and password for HTTP BASIC
243 #
251 #
244 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
252 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
245 # @param [String] url the request url
253 # @param [String] url the request url
246 # @param [optional, Hash] parameters additional request parameters
254 # @param [optional, Hash] parameters additional request parameters
247 # @param [optional, Hash] options additional options
255 # @param [optional, Hash] options additional options
248 # @option options [Symbol] :success_code Successful response code (:success)
256 # @option options [Symbol] :success_code Successful response code (:success)
249 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
257 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
250 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
258 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
251 success_code = options[:success_code] || :success
259 success_code = options[:success_code] || :success
252 failure_code = options[:failure_code] || :unauthorized
260 failure_code = options[:failure_code] || :unauthorized
253
261
254 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
262 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
255 context "with a valid HTTP authentication" do
263 context "with a valid HTTP authentication" do
256 setup do
264 setup do
257 @user = User.generate! do |user|
265 @user = User.generate! do |user|
258 user.admin = true
266 user.admin = true
259 user.password = 'my_password'
267 user.password = 'my_password'
260 end
268 end
261 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
269 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
262 end
270 end
263
271
264 should_respond_with success_code
272 should_respond_with success_code
265 should_respond_with_content_type_based_on_url(url)
273 should_respond_with_content_type_based_on_url(url)
266 should "login as the user" do
274 should "login as the user" do
267 assert_equal @user, User.current
275 assert_equal @user, User.current
268 end
276 end
269 end
277 end
270
278
271 context "with an invalid HTTP authentication" do
279 context "with an invalid HTTP authentication" do
272 setup do
280 setup do
273 @user = User.generate!
281 @user = User.generate!
274 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
282 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
275 end
283 end
276
284
277 should_respond_with failure_code
285 should_respond_with failure_code
278 should_respond_with_content_type_based_on_url(url)
286 should_respond_with_content_type_based_on_url(url)
279 should "not login as the user" do
287 should "not login as the user" do
280 assert_equal User.anonymous, User.current
288 assert_equal User.anonymous, User.current
281 end
289 end
282 end
290 end
283
291
284 context "without credentials" do
292 context "without credentials" do
285 setup do
293 setup do
286 send(http_method, url, parameters)
294 send(http_method, url, parameters)
287 end
295 end
288
296
289 should_respond_with failure_code
297 should_respond_with failure_code
290 should_respond_with_content_type_based_on_url(url)
298 should_respond_with_content_type_based_on_url(url)
291 should "include_www_authenticate_header" do
299 should "include_www_authenticate_header" do
292 assert @controller.response.headers.has_key?('WWW-Authenticate')
300 assert @controller.response.headers.has_key?('WWW-Authenticate')
293 end
301 end
294 end
302 end
295 end
303 end
296 end
304 end
297
305
298 # Test that a request allows the API key with HTTP BASIC
306 # Test that a request allows the API key with HTTP BASIC
299 #
307 #
300 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
308 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
301 # @param [String] url the request url
309 # @param [String] url the request url
302 # @param [optional, Hash] parameters additional request parameters
310 # @param [optional, Hash] parameters additional request parameters
303 # @param [optional, Hash] options additional options
311 # @param [optional, Hash] options additional options
304 # @option options [Symbol] :success_code Successful response code (:success)
312 # @option options [Symbol] :success_code Successful response code (:success)
305 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
313 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
306 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
314 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
307 success_code = options[:success_code] || :success
315 success_code = options[:success_code] || :success
308 failure_code = options[:failure_code] || :unauthorized
316 failure_code = options[:failure_code] || :unauthorized
309
317
310 context "should allow http basic auth with a key for #{http_method} #{url}" do
318 context "should allow http basic auth with a key for #{http_method} #{url}" do
311 context "with a valid HTTP authentication using the API token" do
319 context "with a valid HTTP authentication using the API token" do
312 setup do
320 setup do
313 @user = User.generate! do |user|
321 @user = User.generate! do |user|
314 user.admin = true
322 user.admin = true
315 end
323 end
316 @token = Token.create!(:user => @user, :action => 'api')
324 @token = Token.create!(:user => @user, :action => 'api')
317 send(http_method, url, parameters, credentials(@token.value, 'X'))
325 send(http_method, url, parameters, credentials(@token.value, 'X'))
318 end
326 end
319 should_respond_with success_code
327 should_respond_with success_code
320 should_respond_with_content_type_based_on_url(url)
328 should_respond_with_content_type_based_on_url(url)
321 should_be_a_valid_response_string_based_on_url(url)
329 should_be_a_valid_response_string_based_on_url(url)
322 should "login as the user" do
330 should "login as the user" do
323 assert_equal @user, User.current
331 assert_equal @user, User.current
324 end
332 end
325 end
333 end
326
334
327 context "with an invalid HTTP authentication" do
335 context "with an invalid HTTP authentication" do
328 setup do
336 setup do
329 @user = User.generate!
337 @user = User.generate!
330 @token = Token.create!(:user => @user, :action => 'feeds')
338 @token = Token.create!(:user => @user, :action => 'feeds')
331 send(http_method, url, parameters, credentials(@token.value, 'X'))
339 send(http_method, url, parameters, credentials(@token.value, 'X'))
332 end
340 end
333 should_respond_with failure_code
341 should_respond_with failure_code
334 should_respond_with_content_type_based_on_url(url)
342 should_respond_with_content_type_based_on_url(url)
335 should "not login as the user" do
343 should "not login as the user" do
336 assert_equal User.anonymous, User.current
344 assert_equal User.anonymous, User.current
337 end
345 end
338 end
346 end
339 end
347 end
340 end
348 end
341
349
342 # Test that a request allows full key authentication
350 # Test that a request allows full key authentication
343 #
351 #
344 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
352 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
345 # @param [String] url the request url, without the key=ZXY parameter
353 # @param [String] url the request url, without the key=ZXY parameter
346 # @param [optional, Hash] parameters additional request parameters
354 # @param [optional, Hash] parameters additional request parameters
347 # @param [optional, Hash] options additional options
355 # @param [optional, Hash] options additional options
348 # @option options [Symbol] :success_code Successful response code (:success)
356 # @option options [Symbol] :success_code Successful response code (:success)
349 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
357 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
350 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
358 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
351 success_code = options[:success_code] || :success
359 success_code = options[:success_code] || :success
352 failure_code = options[:failure_code] || :unauthorized
360 failure_code = options[:failure_code] || :unauthorized
353
361
354 context "should allow key based auth using key=X for #{http_method} #{url}" do
362 context "should allow key based auth using key=X for #{http_method} #{url}" do
355 context "with a valid api token" do
363 context "with a valid api token" do
356 setup do
364 setup do
357 @user = User.generate! do |user|
365 @user = User.generate! do |user|
358 user.admin = true
366 user.admin = true
359 end
367 end
360 @token = Token.create!(:user => @user, :action => 'api')
368 @token = Token.create!(:user => @user, :action => 'api')
361 # Simple url parse to add on ?key= or &key=
369 # Simple url parse to add on ?key= or &key=
362 request_url = if url.match(/\?/)
370 request_url = if url.match(/\?/)
363 url + "&key=#{@token.value}"
371 url + "&key=#{@token.value}"
364 else
372 else
365 url + "?key=#{@token.value}"
373 url + "?key=#{@token.value}"
366 end
374 end
367 send(http_method, request_url, parameters)
375 send(http_method, request_url, parameters)
368 end
376 end
369 should_respond_with success_code
377 should_respond_with success_code
370 should_respond_with_content_type_based_on_url(url)
378 should_respond_with_content_type_based_on_url(url)
371 should_be_a_valid_response_string_based_on_url(url)
379 should_be_a_valid_response_string_based_on_url(url)
372 should "login as the user" do
380 should "login as the user" do
373 assert_equal @user, User.current
381 assert_equal @user, User.current
374 end
382 end
375 end
383 end
376
384
377 context "with an invalid api token" do
385 context "with an invalid api token" do
378 setup do
386 setup do
379 @user = User.generate! do |user|
387 @user = User.generate! do |user|
380 user.admin = true
388 user.admin = true
381 end
389 end
382 @token = Token.create!(:user => @user, :action => 'feeds')
390 @token = Token.create!(:user => @user, :action => 'feeds')
383 # Simple url parse to add on ?key= or &key=
391 # Simple url parse to add on ?key= or &key=
384 request_url = if url.match(/\?/)
392 request_url = if url.match(/\?/)
385 url + "&key=#{@token.value}"
393 url + "&key=#{@token.value}"
386 else
394 else
387 url + "?key=#{@token.value}"
395 url + "?key=#{@token.value}"
388 end
396 end
389 send(http_method, request_url, parameters)
397 send(http_method, request_url, parameters)
390 end
398 end
391 should_respond_with failure_code
399 should_respond_with failure_code
392 should_respond_with_content_type_based_on_url(url)
400 should_respond_with_content_type_based_on_url(url)
393 should "not login as the user" do
401 should "not login as the user" do
394 assert_equal User.anonymous, User.current
402 assert_equal User.anonymous, User.current
395 end
403 end
396 end
404 end
397 end
405 end
398
406
399 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
407 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
400 setup do
408 setup do
401 @user = User.generate! do |user|
409 @user = User.generate! do |user|
402 user.admin = true
410 user.admin = true
403 end
411 end
404 @token = Token.create!(:user => @user, :action => 'api')
412 @token = Token.create!(:user => @user, :action => 'api')
405 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
413 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
406 end
414 end
407 should_respond_with success_code
415 should_respond_with success_code
408 should_respond_with_content_type_based_on_url(url)
416 should_respond_with_content_type_based_on_url(url)
409 should_be_a_valid_response_string_based_on_url(url)
417 should_be_a_valid_response_string_based_on_url(url)
410 should "login as the user" do
418 should "login as the user" do
411 assert_equal @user, User.current
419 assert_equal @user, User.current
412 end
420 end
413 end
421 end
414 end
422 end
415
423
416 # Uses should_respond_with_content_type based on what's in the url:
424 # Uses should_respond_with_content_type based on what's in the url:
417 #
425 #
418 # '/project/issues.xml' => should_respond_with_content_type :xml
426 # '/project/issues.xml' => should_respond_with_content_type :xml
419 # '/project/issues.json' => should_respond_with_content_type :json
427 # '/project/issues.json' => should_respond_with_content_type :json
420 #
428 #
421 # @param [String] url Request
429 # @param [String] url Request
422 def self.should_respond_with_content_type_based_on_url(url)
430 def self.should_respond_with_content_type_based_on_url(url)
423 case
431 case
424 when url.match(/xml/i)
432 when url.match(/xml/i)
425 should "respond with XML" do
433 should "respond with XML" do
426 assert_equal 'application/xml', @response.content_type
434 assert_equal 'application/xml', @response.content_type
427 end
435 end
428 when url.match(/json/i)
436 when url.match(/json/i)
429 should "respond with JSON" do
437 should "respond with JSON" do
430 assert_equal 'application/json', @response.content_type
438 assert_equal 'application/json', @response.content_type
431 end
439 end
432 else
440 else
433 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
441 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
434 end
442 end
435 end
443 end
436
444
437 # Uses the url to assert which format the response should be in
445 # Uses the url to assert which format the response should be in
438 #
446 #
439 # '/project/issues.xml' => should_be_a_valid_xml_string
447 # '/project/issues.xml' => should_be_a_valid_xml_string
440 # '/project/issues.json' => should_be_a_valid_json_string
448 # '/project/issues.json' => should_be_a_valid_json_string
441 #
449 #
442 # @param [String] url Request
450 # @param [String] url Request
443 def self.should_be_a_valid_response_string_based_on_url(url)
451 def self.should_be_a_valid_response_string_based_on_url(url)
444 case
452 case
445 when url.match(/xml/i)
453 when url.match(/xml/i)
446 should_be_a_valid_xml_string
454 should_be_a_valid_xml_string
447 when url.match(/json/i)
455 when url.match(/json/i)
448 should_be_a_valid_json_string
456 should_be_a_valid_json_string
449 else
457 else
450 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
458 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
451 end
459 end
452 end
460 end
453
461
454 # Checks that the response is a valid JSON string
462 # Checks that the response is a valid JSON string
455 def self.should_be_a_valid_json_string
463 def self.should_be_a_valid_json_string
456 should "be a valid JSON string (or empty)" do
464 should "be a valid JSON string (or empty)" do
457 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
465 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
458 end
466 end
459 end
467 end
460
468
461 # Checks that the response is a valid XML string
469 # Checks that the response is a valid XML string
462 def self.should_be_a_valid_xml_string
470 def self.should_be_a_valid_xml_string
463 should "be a valid XML string" do
471 should "be a valid XML string" do
464 assert REXML::Document.new(response.body)
472 assert REXML::Document.new(response.body)
465 end
473 end
466 end
474 end
467
475
468 def self.should_respond_with(status)
476 def self.should_respond_with(status)
469 should "respond with #{status}" do
477 should "respond with #{status}" do
470 assert_response status
478 assert_response status
471 end
479 end
472 end
480 end
473 end
481 end
474 end
482 end
475 end
483 end
476
484
477 # URL helpers do not work with config.threadsafe!
485 # URL helpers do not work with config.threadsafe!
478 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
486 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
479 ActionView::TestCase::TestController.instance_eval do
487 ActionView::TestCase::TestController.instance_eval do
480 helper Rails.application.routes.url_helpers
488 helper Rails.application.routes.url_helpers
481 end
489 end
482 ActionView::TestCase::TestController.class_eval do
490 ActionView::TestCase::TestController.class_eval do
483 def _routes
491 def _routes
484 Rails.application.routes
492 Rails.application.routes
485 end
493 end
486 end
494 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