##// END OF EJS Templates
Extracts custom field values validation from CustomValue so that they can be validated globally from the customized object (#1189)....
Jean-Philippe Lang -
r8597:83e7ee6729cd
parent child
Show More
@@ -0,0 +1,50
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class CustomFieldValue
19 attr_accessor :custom_field, :customized, :value
20
21 def custom_field_id
22 custom_field.id
23 end
24
25 def true?
26 self.value == '1'
27 end
28
29 def editable?
30 custom_field.editable?
31 end
32
33 def visible?
34 custom_field.visible?
35 end
36
37 def required?
38 custom_field.is_required?
39 end
40
41 def to_s
42 value.to_s
43 end
44
45 def validate_value
46 custom_field.validate_field_value(value).each do |message|
47 customized.errors.add(:base, custom_field.name + ' ' + message)
48 end
49 end
50 end
@@ -1,120 +1,119
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module CustomFieldsHelper
21 21
22 22 def custom_fields_tabs
23 23 tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
24 24 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
25 25 {:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
26 26 {:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
27 27 {:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
28 28 {:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
29 29 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
30 30 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
31 31 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
32 32 ]
33 33 end
34 34
35 35 # Return custom field html tag corresponding to its format
36 36 def custom_field_tag(name, custom_value)
37 37 custom_field = custom_value.custom_field
38 38 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
39 39 field_id = "#{name}_custom_field_values_#{custom_field.id}"
40 40
41 41 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
42 42 case field_format.try(:edit_as)
43 43 when "date"
44 44 text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
45 45 calendar_for(field_id)
46 46 when "text"
47 47 text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
48 48 when "bool"
49 49 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
50 50 when "list"
51 51 blank_option = custom_field.is_required? ?
52 52 (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
53 53 '<option></option>'
54 54 select_tag(field_name, blank_option.html_safe + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
55 55 else
56 56 text_field_tag(field_name, custom_value.value, :id => field_id)
57 57 end
58 58 end
59 59
60 60 # Return custom field label tag
61 61 def custom_field_label_tag(name, custom_value)
62 62 content_tag "label", h(custom_value.custom_field.name) +
63 63 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
64 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
65 :class => (custom_value.errors.empty? ? nil : "error" )
64 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
66 65 end
67 66
68 67 # Return custom field tag with its label tag
69 68 def custom_field_tag_with_label(name, custom_value)
70 69 custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
71 70 end
72 71
73 72 def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
74 73 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
75 74 field_id = "#{name}_custom_field_values_#{custom_field.id}"
76 75 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
77 76 case field_format.try(:edit_as)
78 77 when "date"
79 78 text_field_tag(field_name, '', :id => field_id, :size => 10) +
80 79 calendar_for(field_id)
81 80 when "text"
82 81 text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
83 82 when "bool"
84 83 select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
85 84 [l(:general_text_yes), '1'],
86 85 [l(:general_text_no), '0']]), :id => field_id)
87 86 when "list"
88 87 select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id)
89 88 else
90 89 text_field_tag(field_name, '', :id => field_id)
91 90 end
92 91 end
93 92
94 93 # Return a string used to display a custom value
95 94 def show_value(custom_value)
96 95 return "" unless custom_value
97 96 format_value(custom_value.value, custom_value.custom_field.field_format)
98 97 end
99 98
100 99 # Return a string used to display a custom value
101 100 def format_value(value, field_format)
102 101 Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
103 102 end
104 103
105 104 # Return an array of custom field formats which can be used in select_tag
106 105 def custom_field_formats_for_select(custom_field)
107 106 Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
108 107 end
109 108
110 109 # Renders the custom_values in api views
111 110 def render_api_custom_values(custom_values, api)
112 111 api.array :custom_fields do
113 112 custom_values.each do |custom_value|
114 113 api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
115 114 api.value custom_value.value
116 115 end
117 116 end
118 117 end unless custom_values.empty?
119 118 end
120 119 end
@@ -1,164 +1,204
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class CustomField < ActiveRecord::Base
19 19 include Redmine::SubclassFactory
20 20
21 21 has_many :custom_values, :dependent => :delete_all
22 22 acts_as_list :scope => 'type = \'#{self.class}\''
23 23 serialize :possible_values
24 24
25 25 validates_presence_of :name, :field_format
26 26 validates_uniqueness_of :name, :scope => :type
27 27 validates_length_of :name, :maximum => 30
28 28 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
29 29
30 validate :validate_values
30 validate :validate_custom_field
31 31 before_validation :set_searchable
32 32
33 33 def initialize(attributes=nil, *args)
34 34 super
35 35 self.possible_values ||= []
36 36 end
37 37
38 38 def set_searchable
39 39 # make sure these fields are not searchable
40 40 self.searchable = false if %w(int float date bool).include?(field_format)
41 41 true
42 42 end
43 43
44 def validate_values
44 def validate_custom_field
45 45 if self.field_format == "list"
46 46 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
47 47 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
48 48 end
49 49
50 50 if regexp.present?
51 51 begin
52 52 Regexp.new(regexp)
53 53 rescue
54 54 errors.add(:regexp, :invalid)
55 55 end
56 56 end
57 57
58 # validate default value
59 v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
60 v.custom_field.is_required = false
61 errors.add(:default_value, :invalid) unless v.valid?
58 unless valid_field_value?(default_value)
59 errors.add(:default_value, :invalid)
60 end
62 61 end
63 62
64 63 def possible_values_options(obj=nil)
65 64 case field_format
66 65 when 'user', 'version'
67 66 if obj.respond_to?(:project) && obj.project
68 67 case field_format
69 68 when 'user'
70 69 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
71 70 when 'version'
72 71 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
73 72 end
74 73 elsif obj.is_a?(Array)
75 74 obj.collect {|o| possible_values_options(o)}.inject {|memo, v| memo & v}
76 75 else
77 76 []
78 77 end
79 78 else
80 79 read_attribute :possible_values
81 80 end
82 81 end
83 82
84 83 def possible_values(obj=nil)
85 84 case field_format
86 85 when 'user', 'version'
87 86 possible_values_options(obj).collect(&:last)
88 87 else
89 88 read_attribute :possible_values
90 89 end
91 90 end
92 91
93 92 # Makes possible_values accept a multiline string
94 93 def possible_values=(arg)
95 94 if arg.is_a?(Array)
96 95 write_attribute(:possible_values, arg.compact.collect(&:strip).select {|v| !v.blank?})
97 96 else
98 97 self.possible_values = arg.to_s.split(/[\n\r]+/)
99 98 end
100 99 end
101 100
102 101 def cast_value(value)
103 102 casted = nil
104 103 unless value.blank?
105 104 case field_format
106 105 when 'string', 'text', 'list'
107 106 casted = value
108 107 when 'date'
109 108 casted = begin; value.to_date; rescue; nil end
110 109 when 'bool'
111 110 casted = (value == '1' ? true : false)
112 111 when 'int'
113 112 casted = value.to_i
114 113 when 'float'
115 114 casted = value.to_f
116 115 when 'user', 'version'
117 116 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
118 117 end
119 118 end
120 119 casted
121 120 end
122 121
123 122 # Returns a ORDER BY clause that can used to sort customized
124 123 # objects by their value of the custom field.
125 124 # Returns false, if the custom field can not be used for sorting.
126 125 def order_statement
127 126 case field_format
128 127 when 'string', 'text', 'list', 'date', 'bool'
129 128 # COALESCE is here to make sure that blank and NULL values are sorted equally
130 129 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
131 130 " WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
132 131 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
133 132 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
134 133 when 'int', 'float'
135 134 # Make the database cast values into numeric
136 135 # Postgresql will raise an error if a value can not be casted!
137 136 # CustomValue validations should ensure that it doesn't occur
138 137 "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
139 138 " WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
140 139 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
141 140 " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
142 141 else
143 142 nil
144 143 end
145 144 end
146 145
147 146 def <=>(field)
148 147 position <=> field.position
149 148 end
150 149
151 150 def self.customized_class
152 151 self.name =~ /^(.+)CustomField$/
153 152 begin; $1.constantize; rescue nil; end
154 153 end
155 154
156 155 # to move in project_custom_field
157 156 def self.for_all
158 157 find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
159 158 end
160 159
161 160 def type_name
162 161 nil
163 162 end
163
164 # Returns the error message for the given value
165 # or an empty array if value is a valid value for the custom field
166 def validate_field_value(value)
167 errs = []
168 if is_required? && value.blank?
169 errs << ::I18n.t('activerecord.errors.messages.blank')
170 end
171 errs += validate_field_value_format(value)
172 errs
173 end
174
175 # Returns true if value is a valid value for the custom field
176 def valid_field_value?(value)
177 validate_field_value(value).empty?
178 end
179
180 protected
181
182 # Returns the error message for the given value regarding its format
183 def validate_field_value_format(value)
184 errs = []
185 if value.present?
186 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
187 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
188 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
189
190 # Format specific validations
191 case field_format
192 when 'int'
193 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
194 when 'float'
195 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
196 when 'date'
197 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
198 when 'list'
199 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
200 end
201 end
202 errs
203 end
164 204 end
@@ -1,74 +1,49
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class CustomValue < ActiveRecord::Base
19 19 belongs_to :custom_field
20 20 belongs_to :customized, :polymorphic => true
21 21
22 validate :validate_custom_value
23
24 22 def initialize(attributes=nil, *args)
25 23 super
26 24 if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?))
27 25 self.value ||= custom_field.default_value
28 26 end
29 27 end
30 28
31 29 # Returns true if the boolean custom value is true
32 30 def true?
33 31 self.value == '1'
34 32 end
35 33
36 34 def editable?
37 35 custom_field.editable?
38 36 end
39 37
40 38 def visible?
41 39 custom_field.visible?
42 40 end
43 41
44 42 def required?
45 43 custom_field.is_required?
46 44 end
47 45
48 46 def to_s
49 47 value.to_s
50 48 end
51
52 protected
53 def validate_custom_value
54 if value.blank?
55 errors.add(:value, :blank) if custom_field.is_required? and value.blank?
56 else
57 errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
58 errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length
59 errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length
60
61 # Format specific validations
62 case custom_field.field_format
63 when 'int'
64 errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
65 when 'float'
66 begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
67 when 'date'
68 errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
69 when 'list'
70 errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
71 end
72 end
73 end
74 49 end
@@ -1,118 +1,88
1 1 # Patches active_support/core_ext/load_error.rb to support 1.9.3 LoadError message
2 2 if RUBY_VERSION >= '1.9.3'
3 3 MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1]
4 4 end
5 5
6 6 require 'active_record'
7 7
8 8 module ActiveRecord
9 9 class Base
10 10 include Redmine::I18n
11 11
12 12 # Translate attribute names for validation errors display
13 13 def self.human_attribute_name(attr, *args)
14 14 l("field_#{attr.to_s.gsub(/_id$/, '')}", :default => attr)
15 15 end
16 16 end
17 17 end
18 18
19 module ActiveRecord
20 class Errors
21 def full_messages(options = {})
22 full_messages = []
23
24 @errors.each_key do |attr|
25 @errors[attr].each do |message|
26 next unless message
27
28 if attr == "base"
29 full_messages << message
30 elsif attr == "custom_values"
31 # Replace the generic "custom values is invalid"
32 # with the errors on custom values
33 @base.custom_values.each do |value|
34 value.errors.each do |attr, msg|
35 full_messages << value.custom_field.name + ' ' + msg
36 end
37 end
38 else
39 attr_name = @base.class.human_attribute_name(attr)
40 full_messages << attr_name + ' ' + message.to_s
41 end
42 end
43 end
44 full_messages
45 end
46 end
47 end
48
49 19 module ActionView
50 20 module Helpers
51 21 module DateHelper
52 22 # distance_of_time_in_words breaks when difference is greater than 30 years
53 23 def distance_of_date_in_words(from_date, to_date = 0, options = {})
54 24 from_date = from_date.to_date if from_date.respond_to?(:to_date)
55 25 to_date = to_date.to_date if to_date.respond_to?(:to_date)
56 26 distance_in_days = (to_date - from_date).abs
57 27
58 28 I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
59 29 case distance_in_days
60 30 when 0..60 then locale.t :x_days, :count => distance_in_days.round
61 31 when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round
62 32 else locale.t :over_x_years, :count => (distance_in_days / 365).floor
63 33 end
64 34 end
65 35 end
66 36 end
67 37 end
68 38 end
69 39
70 40 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
71 41
72 42 module AsynchronousMailer
73 43 # Adds :async_smtp and :async_sendmail delivery methods
74 44 # to perform email deliveries asynchronously
75 45 %w(smtp sendmail).each do |type|
76 46 define_method("perform_delivery_async_#{type}") do |mail|
77 47 Thread.start do
78 48 send "perform_delivery_#{type}", mail
79 49 end
80 50 end
81 51 end
82 52
83 53 # Adds a delivery method that writes emails in tmp/emails for testing purpose
84 54 def perform_delivery_tmp_file(mail)
85 55 dest_dir = File.join(Rails.root, 'tmp', 'emails')
86 56 Dir.mkdir(dest_dir) unless File.directory?(dest_dir)
87 57 File.open(File.join(dest_dir, mail.message_id.gsub(/[<>]/, '') + '.eml'), 'wb') {|f| f.write(mail.encoded) }
88 58 end
89 59 end
90 60
91 61 ActionMailer::Base.send :include, AsynchronousMailer
92 62
93 63 module TMail
94 64 # TMail::Unquoter.convert_to_with_fallback_on_iso_8859_1 introduced in TMail 1.2.7
95 65 # triggers a test failure in test_add_issue_with_japanese_keywords(MailHandlerTest)
96 66 class Unquoter
97 67 class << self
98 68 alias_method :convert_to, :convert_to_without_fallback_on_iso_8859_1
99 69 end
100 70 end
101 71
102 72 # Patch for TMail 1.2.7. See http://www.redmine.org/issues/8751
103 73 class Encoder
104 74 def puts_meta(str)
105 75 add_text str
106 76 end
107 77 end
108 78 end
109 79
110 80 module ActionController
111 81 module MimeResponds
112 82 class Responder
113 83 def api(&block)
114 84 any(:xml, :json, &block)
115 85 end
116 86 end
117 87 end
118 88 end
@@ -1,2679 +1,2680
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19 require 'issues_controller'
20 20
21 21 class IssuesControllerTest < ActionController::TestCase
22 22 fixtures :projects,
23 23 :users,
24 24 :roles,
25 25 :members,
26 26 :member_roles,
27 27 :issues,
28 28 :issue_statuses,
29 29 :versions,
30 30 :trackers,
31 31 :projects_trackers,
32 32 :issue_categories,
33 33 :enabled_modules,
34 34 :enumerations,
35 35 :attachments,
36 36 :workflows,
37 37 :custom_fields,
38 38 :custom_values,
39 39 :custom_fields_projects,
40 40 :custom_fields_trackers,
41 41 :time_entries,
42 42 :journals,
43 43 :journal_details,
44 44 :queries
45 45
46 46 include Redmine::I18n
47 47
48 48 def setup
49 49 @controller = IssuesController.new
50 50 @request = ActionController::TestRequest.new
51 51 @response = ActionController::TestResponse.new
52 52 User.current = nil
53 53 end
54 54
55 55 def test_index
56 56 Setting.default_language = 'en'
57 57
58 58 get :index
59 59 assert_response :success
60 60 assert_template 'index'
61 61 assert_not_nil assigns(:issues)
62 62 assert_nil assigns(:project)
63 63 assert_tag :tag => 'a', :content => /Can't print recipes/
64 64 assert_tag :tag => 'a', :content => /Subproject issue/
65 65 # private projects hidden
66 66 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 67 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 68 # project column
69 69 assert_tag :tag => 'th', :content => /Project/
70 70 end
71 71
72 72 def test_index_should_not_list_issues_when_module_disabled
73 73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 74 get :index
75 75 assert_response :success
76 76 assert_template 'index'
77 77 assert_not_nil assigns(:issues)
78 78 assert_nil assigns(:project)
79 79 assert_no_tag :tag => 'a', :content => /Can't print recipes/
80 80 assert_tag :tag => 'a', :content => /Subproject issue/
81 81 end
82 82
83 83 def test_index_should_list_visible_issues_only
84 84 get :index, :per_page => 100
85 85 assert_response :success
86 86 assert_not_nil assigns(:issues)
87 87 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
88 88 end
89 89
90 90 def test_index_with_project
91 91 Setting.display_subprojects_issues = 0
92 92 get :index, :project_id => 1
93 93 assert_response :success
94 94 assert_template 'index'
95 95 assert_not_nil assigns(:issues)
96 96 assert_tag :tag => 'a', :content => /Can't print recipes/
97 97 assert_no_tag :tag => 'a', :content => /Subproject issue/
98 98 end
99 99
100 100 def test_index_with_project_and_subprojects
101 101 Setting.display_subprojects_issues = 1
102 102 get :index, :project_id => 1
103 103 assert_response :success
104 104 assert_template 'index'
105 105 assert_not_nil assigns(:issues)
106 106 assert_tag :tag => 'a', :content => /Can't print recipes/
107 107 assert_tag :tag => 'a', :content => /Subproject issue/
108 108 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
109 109 end
110 110
111 111 def test_index_with_project_and_subprojects_should_show_private_subprojects
112 112 @request.session[:user_id] = 2
113 113 Setting.display_subprojects_issues = 1
114 114 get :index, :project_id => 1
115 115 assert_response :success
116 116 assert_template 'index'
117 117 assert_not_nil assigns(:issues)
118 118 assert_tag :tag => 'a', :content => /Can't print recipes/
119 119 assert_tag :tag => 'a', :content => /Subproject issue/
120 120 assert_tag :tag => 'a', :content => /Issue of a private subproject/
121 121 end
122 122
123 123 def test_index_with_project_and_default_filter
124 124 get :index, :project_id => 1, :set_filter => 1
125 125 assert_response :success
126 126 assert_template 'index'
127 127 assert_not_nil assigns(:issues)
128 128
129 129 query = assigns(:query)
130 130 assert_not_nil query
131 131 # default filter
132 132 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
133 133 end
134 134
135 135 def test_index_with_project_and_filter
136 136 get :index, :project_id => 1, :set_filter => 1,
137 137 :f => ['tracker_id'],
138 138 :op => {'tracker_id' => '='},
139 139 :v => {'tracker_id' => ['1']}
140 140 assert_response :success
141 141 assert_template 'index'
142 142 assert_not_nil assigns(:issues)
143 143
144 144 query = assigns(:query)
145 145 assert_not_nil query
146 146 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
147 147 end
148 148
149 149 def test_index_with_short_filters
150 150
151 151 to_test = {
152 152 'status_id' => {
153 153 'o' => { :op => 'o', :values => [''] },
154 154 'c' => { :op => 'c', :values => [''] },
155 155 '7' => { :op => '=', :values => ['7'] },
156 156 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
157 157 '=7' => { :op => '=', :values => ['7'] },
158 158 '!3' => { :op => '!', :values => ['3'] },
159 159 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
160 160 'subject' => {
161 161 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
162 162 'o' => { :op => '=', :values => ['o'] },
163 163 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
164 164 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
165 165 'tracker_id' => {
166 166 '3' => { :op => '=', :values => ['3'] },
167 167 '=3' => { :op => '=', :values => ['3'] }},
168 168 'start_date' => {
169 169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 174 '<t+2' => { :op => '<t+', :values => ['2'] },
175 175 '>t+2' => { :op => '>t+', :values => ['2'] },
176 176 't+2' => { :op => 't+', :values => ['2'] },
177 177 't' => { :op => 't', :values => [''] },
178 178 'w' => { :op => 'w', :values => [''] },
179 179 '>t-2' => { :op => '>t-', :values => ['2'] },
180 180 '<t-2' => { :op => '<t-', :values => ['2'] },
181 181 't-2' => { :op => 't-', :values => ['2'] }},
182 182 'created_on' => {
183 183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 184 '<t+2' => { :op => '=', :values => ['<t+2'] },
185 185 '>t+2' => { :op => '=', :values => ['>t+2'] },
186 186 't+2' => { :op => 't', :values => ['+2'] }},
187 187 'cf_1' => {
188 188 'c' => { :op => '=', :values => ['c'] },
189 189 '!c' => { :op => '!', :values => ['c'] },
190 190 '!*' => { :op => '!*', :values => [''] },
191 191 '*' => { :op => '*', :values => [''] }},
192 192 'estimated_hours' => {
193 193 '=13.4' => { :op => '=', :values => ['13.4'] },
194 194 '>=45' => { :op => '>=', :values => ['45'] },
195 195 '<=125' => { :op => '<=', :values => ['125'] },
196 196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 197 '!*' => { :op => '!*', :values => [''] },
198 198 '*' => { :op => '*', :values => [''] }}
199 199 }
200 200
201 201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202 202
203 203 to_test.each do |field, expression_and_expected|
204 204 expression_and_expected.each do |filter_expression, expected|
205 205
206 206 get :index, :set_filter => 1, field => filter_expression
207 207
208 208 assert_response :success
209 209 assert_template 'index'
210 210 assert_not_nil assigns(:issues)
211 211
212 212 query = assigns(:query)
213 213 assert_not_nil query
214 214 assert query.has_filter?(field)
215 215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 216 end
217 217 end
218 218
219 219 end
220 220
221 221 def test_index_with_project_and_empty_filters
222 222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
223 223 assert_response :success
224 224 assert_template 'index'
225 225 assert_not_nil assigns(:issues)
226 226
227 227 query = assigns(:query)
228 228 assert_not_nil query
229 229 # no filter
230 230 assert_equal({}, query.filters)
231 231 end
232 232
233 233 def test_index_with_query
234 234 get :index, :project_id => 1, :query_id => 5
235 235 assert_response :success
236 236 assert_template 'index'
237 237 assert_not_nil assigns(:issues)
238 238 assert_nil assigns(:issue_count_by_group)
239 239 end
240 240
241 241 def test_index_with_query_grouped_by_tracker
242 242 get :index, :project_id => 1, :query_id => 6
243 243 assert_response :success
244 244 assert_template 'index'
245 245 assert_not_nil assigns(:issues)
246 246 assert_not_nil assigns(:issue_count_by_group)
247 247 end
248 248
249 249 def test_index_with_query_grouped_by_list_custom_field
250 250 get :index, :project_id => 1, :query_id => 9
251 251 assert_response :success
252 252 assert_template 'index'
253 253 assert_not_nil assigns(:issues)
254 254 assert_not_nil assigns(:issue_count_by_group)
255 255 end
256 256
257 257 def test_index_with_query_id_and_project_id_should_set_session_query
258 258 get :index, :project_id => 1, :query_id => 4
259 259 assert_response :success
260 260 assert_kind_of Hash, session[:query]
261 261 assert_equal 4, session[:query][:id]
262 262 assert_equal 1, session[:query][:project_id]
263 263 end
264 264
265 265 def test_index_with_cross_project_query_in_session_should_show_project_issues
266 266 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
267 267 @request.session[:query] = {:id => q.id, :project_id => 1}
268 268
269 269 with_settings :display_subprojects_issues => '0' do
270 270 get :index, :project_id => 1
271 271 end
272 272 assert_response :success
273 273 assert_not_nil assigns(:query)
274 274 assert_equal q.id, assigns(:query).id
275 275 assert_equal 1, assigns(:query).project_id
276 276 assert_equal [1], assigns(:issues).map(&:project_id).uniq
277 277 end
278 278
279 279 def test_private_query_should_not_be_available_to_other_users
280 280 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
281 281 @request.session[:user_id] = 3
282 282
283 283 get :index, :query_id => q.id
284 284 assert_response 403
285 285 end
286 286
287 287 def test_private_query_should_be_available_to_its_user
288 288 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
289 289 @request.session[:user_id] = 2
290 290
291 291 get :index, :query_id => q.id
292 292 assert_response :success
293 293 end
294 294
295 295 def test_public_query_should_be_available_to_other_users
296 296 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
297 297 @request.session[:user_id] = 3
298 298
299 299 get :index, :query_id => q.id
300 300 assert_response :success
301 301 end
302 302
303 303 def test_index_csv
304 304 get :index, :format => 'csv'
305 305 assert_response :success
306 306 assert_not_nil assigns(:issues)
307 307 assert_equal 'text/csv', @response.content_type
308 308 assert @response.body.starts_with?("#,")
309 309 lines = @response.body.chomp.split("\n")
310 310 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
311 311 end
312 312
313 313 def test_index_csv_with_project
314 314 get :index, :project_id => 1, :format => 'csv'
315 315 assert_response :success
316 316 assert_not_nil assigns(:issues)
317 317 assert_equal 'text/csv', @response.content_type
318 318 end
319 319
320 320 def test_index_csv_with_description
321 321 get :index, :format => 'csv', :description => '1'
322 322 assert_response :success
323 323 assert_not_nil assigns(:issues)
324 324 assert_equal 'text/csv', @response.content_type
325 325 assert @response.body.starts_with?("#,")
326 326 lines = @response.body.chomp.split("\n")
327 327 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
328 328 end
329 329
330 330 def test_index_csv_with_all_columns
331 331 get :index, :format => 'csv', :columns => 'all'
332 332 assert_response :success
333 333 assert_not_nil assigns(:issues)
334 334 assert_equal 'text/csv', @response.content_type
335 335 assert @response.body.starts_with?("#,")
336 336 lines = @response.body.chomp.split("\n")
337 337 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
338 338 end
339 339
340 340 def test_index_csv_big_5
341 341 with_settings :default_language => "zh-TW" do
342 342 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
343 343 str_big5 = "\xa4@\xa4\xeb"
344 344 if str_utf8.respond_to?(:force_encoding)
345 345 str_utf8.force_encoding('UTF-8')
346 346 str_big5.force_encoding('Big5')
347 347 end
348 348 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
349 349 :status_id => 1, :priority => IssuePriority.all.first,
350 350 :subject => str_utf8)
351 351 assert issue.save
352 352
353 353 get :index, :project_id => 1,
354 354 :f => ['subject'],
355 355 :op => '=', :values => [str_utf8],
356 356 :format => 'csv'
357 357 assert_equal 'text/csv', @response.content_type
358 358 lines = @response.body.chomp.split("\n")
359 359 s1 = "\xaa\xac\xbaA"
360 360 if str_utf8.respond_to?(:force_encoding)
361 361 s1.force_encoding('Big5')
362 362 end
363 363 assert lines[0].include?(s1)
364 364 assert lines[1].include?(str_big5)
365 365 end
366 366 end
367 367
368 368 def test_index_csv_cannot_convert_should_be_replaced_big_5
369 369 with_settings :default_language => "zh-TW" do
370 370 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
371 371 if str_utf8.respond_to?(:force_encoding)
372 372 str_utf8.force_encoding('UTF-8')
373 373 end
374 374 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
375 375 :status_id => 1, :priority => IssuePriority.all.first,
376 376 :subject => str_utf8)
377 377 assert issue.save
378 378
379 379 get :index, :project_id => 1,
380 380 :f => ['subject'],
381 381 :op => '=', :values => [str_utf8],
382 382 :c => ['status', 'subject'],
383 383 :format => 'csv',
384 384 :set_filter => 1
385 385 assert_equal 'text/csv', @response.content_type
386 386 lines = @response.body.chomp.split("\n")
387 387 s1 = "\xaa\xac\xbaA" # status
388 388 if str_utf8.respond_to?(:force_encoding)
389 389 s1.force_encoding('Big5')
390 390 end
391 391 assert lines[0].include?(s1)
392 392 s2 = lines[1].split(",")[2]
393 393 if s1.respond_to?(:force_encoding)
394 394 s3 = "\xa5H?" # subject
395 395 s3.force_encoding('Big5')
396 396 assert_equal s3, s2
397 397 elsif RUBY_PLATFORM == 'java'
398 398 assert_equal "??", s2
399 399 else
400 400 assert_equal "\xa5H???", s2
401 401 end
402 402 end
403 403 end
404 404
405 405 def test_index_csv_tw
406 406 with_settings :default_language => "zh-TW" do
407 407 str1 = "test_index_csv_tw"
408 408 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
409 409 :status_id => 1, :priority => IssuePriority.all.first,
410 410 :subject => str1, :estimated_hours => '1234.5')
411 411 assert issue.save
412 412 assert_equal 1234.5, issue.estimated_hours
413 413
414 414 get :index, :project_id => 1,
415 415 :f => ['subject'],
416 416 :op => '=', :values => [str1],
417 417 :c => ['estimated_hours', 'subject'],
418 418 :format => 'csv',
419 419 :set_filter => 1
420 420 assert_equal 'text/csv', @response.content_type
421 421 lines = @response.body.chomp.split("\n")
422 422 assert_equal "#{issue.id},1234.5,#{str1}", lines[1]
423 423
424 424 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
425 425 if str_tw.respond_to?(:force_encoding)
426 426 str_tw.force_encoding('UTF-8')
427 427 end
428 428 assert_equal str_tw, l(:general_lang_name)
429 429 assert_equal ',', l(:general_csv_separator)
430 430 assert_equal '.', l(:general_csv_decimal_separator)
431 431 end
432 432 end
433 433
434 434 def test_index_csv_fr
435 435 with_settings :default_language => "fr" do
436 436 str1 = "test_index_csv_fr"
437 437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
438 438 :status_id => 1, :priority => IssuePriority.all.first,
439 439 :subject => str1, :estimated_hours => '1234.5')
440 440 assert issue.save
441 441 assert_equal 1234.5, issue.estimated_hours
442 442
443 443 get :index, :project_id => 1,
444 444 :f => ['subject'],
445 445 :op => '=', :values => [str1],
446 446 :c => ['estimated_hours', 'subject'],
447 447 :format => 'csv',
448 448 :set_filter => 1
449 449 assert_equal 'text/csv', @response.content_type
450 450 lines = @response.body.chomp.split("\n")
451 451 assert_equal "#{issue.id};1234,5;#{str1}", lines[1]
452 452
453 453 str_fr = "Fran\xc3\xa7ais"
454 454 if str_fr.respond_to?(:force_encoding)
455 455 str_fr.force_encoding('UTF-8')
456 456 end
457 457 assert_equal str_fr, l(:general_lang_name)
458 458 assert_equal ';', l(:general_csv_separator)
459 459 assert_equal ',', l(:general_csv_decimal_separator)
460 460 end
461 461 end
462 462
463 463 def test_index_pdf
464 464 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
465 465 with_settings :default_language => lang do
466 466
467 467 get :index
468 468 assert_response :success
469 469 assert_template 'index'
470 470
471 471 if lang == "ja"
472 472 if RUBY_PLATFORM != 'java'
473 473 assert_equal "CP932", l(:general_pdf_encoding)
474 474 end
475 475 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
476 476 next
477 477 end
478 478 end
479 479
480 480 get :index, :format => 'pdf'
481 481 assert_response :success
482 482 assert_not_nil assigns(:issues)
483 483 assert_equal 'application/pdf', @response.content_type
484 484
485 485 get :index, :project_id => 1, :format => 'pdf'
486 486 assert_response :success
487 487 assert_not_nil assigns(:issues)
488 488 assert_equal 'application/pdf', @response.content_type
489 489
490 490 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
491 491 assert_response :success
492 492 assert_not_nil assigns(:issues)
493 493 assert_equal 'application/pdf', @response.content_type
494 494 end
495 495 end
496 496 end
497 497
498 498 def test_index_pdf_with_query_grouped_by_list_custom_field
499 499 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
500 500 assert_response :success
501 501 assert_not_nil assigns(:issues)
502 502 assert_not_nil assigns(:issue_count_by_group)
503 503 assert_equal 'application/pdf', @response.content_type
504 504 end
505 505
506 506 def test_index_sort
507 507 get :index, :sort => 'tracker,id:desc'
508 508 assert_response :success
509 509
510 510 sort_params = @request.session['issues_index_sort']
511 511 assert sort_params.is_a?(String)
512 512 assert_equal 'tracker,id:desc', sort_params
513 513
514 514 issues = assigns(:issues)
515 515 assert_not_nil issues
516 516 assert !issues.empty?
517 517 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
518 518 end
519 519
520 520 def test_index_sort_by_field_not_included_in_columns
521 521 Setting.issue_list_default_columns = %w(subject author)
522 522 get :index, :sort => 'tracker'
523 523 end
524 524
525 525 def test_index_sort_by_assigned_to
526 526 get :index, :sort => 'assigned_to'
527 527 assert_response :success
528 528 assignees = assigns(:issues).collect(&:assigned_to).compact
529 529 assert_equal assignees.sort, assignees
530 530 end
531 531
532 532 def test_index_sort_by_assigned_to_desc
533 533 get :index, :sort => 'assigned_to:desc'
534 534 assert_response :success
535 535 assignees = assigns(:issues).collect(&:assigned_to).compact
536 536 assert_equal assignees.sort.reverse, assignees
537 537 end
538 538
539 539 def test_index_group_by_assigned_to
540 540 get :index, :group_by => 'assigned_to', :sort => 'priority'
541 541 assert_response :success
542 542 end
543 543
544 544 def test_index_sort_by_author
545 545 get :index, :sort => 'author'
546 546 assert_response :success
547 547 authors = assigns(:issues).collect(&:author)
548 548 assert_equal authors.sort, authors
549 549 end
550 550
551 551 def test_index_sort_by_author_desc
552 552 get :index, :sort => 'author:desc'
553 553 assert_response :success
554 554 authors = assigns(:issues).collect(&:author)
555 555 assert_equal authors.sort.reverse, authors
556 556 end
557 557
558 558 def test_index_group_by_author
559 559 get :index, :group_by => 'author', :sort => 'priority'
560 560 assert_response :success
561 561 end
562 562
563 563 def test_index_sort_by_spent_hours
564 564 get :index, :sort => 'spent_hours:desc'
565 565 assert_response :success
566 566 hours = assigns(:issues).collect(&:spent_hours)
567 567 assert_equal hours.sort.reverse, hours
568 568 end
569 569
570 570 def test_index_with_columns
571 571 columns = ['tracker', 'subject', 'assigned_to']
572 572 get :index, :set_filter => 1, :c => columns
573 573 assert_response :success
574 574
575 575 # query should use specified columns
576 576 query = assigns(:query)
577 577 assert_kind_of Query, query
578 578 assert_equal columns, query.column_names.map(&:to_s)
579 579
580 580 # columns should be stored in session
581 581 assert_kind_of Hash, session[:query]
582 582 assert_kind_of Array, session[:query][:column_names]
583 583 assert_equal columns, session[:query][:column_names].map(&:to_s)
584 584
585 585 # ensure only these columns are kept in the selected columns list
586 586 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
587 587 :children => { :count => 3 }
588 588 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
589 589 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
590 590 end
591 591
592 592 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
593 593 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
594 594 get :index, :set_filter => 1
595 595
596 596 # query should use specified columns
597 597 query = assigns(:query)
598 598 assert_kind_of Query, query
599 599 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
600 600 end
601 601
602 602 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
603 603 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
604 604 columns = ['tracker', 'subject', 'assigned_to']
605 605 get :index, :set_filter => 1, :c => columns
606 606
607 607 # query should use specified columns
608 608 query = assigns(:query)
609 609 assert_kind_of Query, query
610 610 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
611 611 end
612 612
613 613 def test_index_with_custom_field_column
614 614 columns = %w(tracker subject cf_2)
615 615 get :index, :set_filter => 1, :c => columns
616 616 assert_response :success
617 617
618 618 # query should use specified columns
619 619 query = assigns(:query)
620 620 assert_kind_of Query, query
621 621 assert_equal columns, query.column_names.map(&:to_s)
622 622
623 623 assert_tag :td,
624 624 :attributes => {:class => 'cf_2 string'},
625 625 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
626 626 end
627 627
628 628 def test_index_with_date_column
629 629 Issue.find(1).update_attribute :start_date, '1987-08-24'
630 630
631 631 with_settings :date_format => '%d/%m/%Y' do
632 632 get :index, :set_filter => 1, :c => %w(start_date)
633 633 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
634 634 end
635 635 end
636 636
637 637 def test_index_with_done_ratio
638 638 Issue.find(1).update_attribute :done_ratio, 40
639 639
640 640 get :index, :set_filter => 1, :c => %w(done_ratio)
641 641 assert_tag 'td', :attributes => {:class => /done_ratio/},
642 642 :child => {:tag => 'table', :attributes => {:class => 'progress'},
643 643 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
644 644 }
645 645 end
646 646
647 647 def test_index_with_spent_hours_column
648 648 get :index, :set_filter => 1, :c => %w(subject spent_hours)
649 649
650 650 assert_tag 'tr', :attributes => {:id => 'issue-3'},
651 651 :child => {
652 652 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
653 653 }
654 654 end
655 655
656 656 def test_index_should_not_show_spent_hours_column_without_permission
657 657 Role.anonymous.remove_permission! :view_time_entries
658 658 get :index, :set_filter => 1, :c => %w(subject spent_hours)
659 659
660 660 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
661 661 end
662 662
663 663 def test_index_with_fixed_version
664 664 get :index, :set_filter => 1, :c => %w(fixed_version)
665 665 assert_tag 'td', :attributes => {:class => /fixed_version/},
666 666 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
667 667 end
668 668
669 669 def test_index_send_html_if_query_is_invalid
670 670 get :index, :f => ['start_date'], :op => {:start_date => '='}
671 671 assert_equal 'text/html', @response.content_type
672 672 assert_template 'index'
673 673 end
674 674
675 675 def test_index_send_nothing_if_query_is_invalid
676 676 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
677 677 assert_equal 'text/csv', @response.content_type
678 678 assert @response.body.blank?
679 679 end
680 680
681 681 def test_show_by_anonymous
682 682 get :show, :id => 1
683 683 assert_response :success
684 684 assert_template 'show'
685 685 assert_not_nil assigns(:issue)
686 686 assert_equal Issue.find(1), assigns(:issue)
687 687
688 688 # anonymous role is allowed to add a note
689 689 assert_tag :tag => 'form',
690 690 :descendant => { :tag => 'fieldset',
691 691 :child => { :tag => 'legend',
692 692 :content => /Notes/ } }
693 693 assert_tag :tag => 'title',
694 694 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
695 695 end
696 696
697 697 def test_show_by_manager
698 698 @request.session[:user_id] = 2
699 699 get :show, :id => 1
700 700 assert_response :success
701 701
702 702 assert_tag :tag => 'a',
703 703 :content => /Quote/
704 704
705 705 assert_tag :tag => 'form',
706 706 :descendant => { :tag => 'fieldset',
707 707 :child => { :tag => 'legend',
708 708 :content => /Change properties/ } },
709 709 :descendant => { :tag => 'fieldset',
710 710 :child => { :tag => 'legend',
711 711 :content => /Log time/ } },
712 712 :descendant => { :tag => 'fieldset',
713 713 :child => { :tag => 'legend',
714 714 :content => /Notes/ } }
715 715 end
716 716
717 717 def test_show_should_display_update_form
718 718 @request.session[:user_id] = 2
719 719 get :show, :id => 1
720 720 assert_response :success
721 721
722 722 assert_tag 'form', :attributes => {:id => 'issue-form'}
723 723 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
724 724 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
725 725 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
726 726 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
727 727 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
728 728 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
729 729 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
730 730 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
731 731 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
732 732 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
733 733 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
734 734 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
735 735 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
736 736 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
737 737 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
738 738 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
739 739 assert_tag 'textarea', :attributes => {:name => 'notes'}
740 740 end
741 741
742 742 def test_show_should_display_update_form_with_minimal_permissions
743 743 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
744 744 Workflow.delete_all :role_id => 1
745 745
746 746 @request.session[:user_id] = 2
747 747 get :show, :id => 1
748 748 assert_response :success
749 749
750 750 assert_tag 'form', :attributes => {:id => 'issue-form'}
751 751 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
752 752 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
753 753 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
754 754 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
755 755 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
756 756 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
757 757 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
758 758 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
759 759 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
760 760 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
761 761 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
762 762 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
763 763 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
764 764 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
765 765 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
766 766 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
767 767 assert_tag 'textarea', :attributes => {:name => 'notes'}
768 768 end
769 769
770 770 def test_show_should_display_update_form_with_workflow_permissions
771 771 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
772 772
773 773 @request.session[:user_id] = 2
774 774 get :show, :id => 1
775 775 assert_response :success
776 776
777 777 assert_tag 'form', :attributes => {:id => 'issue-form'}
778 778 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
779 779 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
780 780 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
781 781 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
782 782 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
783 783 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
784 784 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
785 785 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
786 786 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
787 787 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
788 788 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
789 789 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
790 790 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
791 791 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
792 792 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
793 793 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
794 794 assert_tag 'textarea', :attributes => {:name => 'notes'}
795 795 end
796 796
797 797 def test_show_should_not_display_update_form_without_permissions
798 798 Role.find(1).update_attribute :permissions, [:view_issues]
799 799
800 800 @request.session[:user_id] = 2
801 801 get :show, :id => 1
802 802 assert_response :success
803 803
804 804 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
805 805 end
806 806
807 807 def test_update_form_should_not_display_inactive_enumerations
808 808 @request.session[:user_id] = 2
809 809 get :show, :id => 1
810 810 assert_response :success
811 811
812 812 assert ! IssuePriority.find(15).active?
813 813 assert_no_tag :option, :attributes => {:value => '15'},
814 814 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
815 815 end
816 816
817 817 def test_update_form_should_allow_attachment_upload
818 818 @request.session[:user_id] = 2
819 819 get :show, :id => 1
820 820
821 821 assert_tag :tag => 'form',
822 822 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
823 823 :descendant => {
824 824 :tag => 'input',
825 825 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
826 826 }
827 827 end
828 828
829 829 def test_show_should_deny_anonymous_access_without_permission
830 830 Role.anonymous.remove_permission!(:view_issues)
831 831 get :show, :id => 1
832 832 assert_response :redirect
833 833 end
834 834
835 835 def test_show_should_deny_anonymous_access_to_private_issue
836 836 Issue.update_all(["is_private = ?", true], "id = 1")
837 837 get :show, :id => 1
838 838 assert_response :redirect
839 839 end
840 840
841 841 def test_show_should_deny_non_member_access_without_permission
842 842 Role.non_member.remove_permission!(:view_issues)
843 843 @request.session[:user_id] = 9
844 844 get :show, :id => 1
845 845 assert_response 403
846 846 end
847 847
848 848 def test_show_should_deny_non_member_access_to_private_issue
849 849 Issue.update_all(["is_private = ?", true], "id = 1")
850 850 @request.session[:user_id] = 9
851 851 get :show, :id => 1
852 852 assert_response 403
853 853 end
854 854
855 855 def test_show_should_deny_member_access_without_permission
856 856 Role.find(1).remove_permission!(:view_issues)
857 857 @request.session[:user_id] = 2
858 858 get :show, :id => 1
859 859 assert_response 403
860 860 end
861 861
862 862 def test_show_should_deny_member_access_to_private_issue_without_permission
863 863 Issue.update_all(["is_private = ?", true], "id = 1")
864 864 @request.session[:user_id] = 3
865 865 get :show, :id => 1
866 866 assert_response 403
867 867 end
868 868
869 869 def test_show_should_allow_author_access_to_private_issue
870 870 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
871 871 @request.session[:user_id] = 3
872 872 get :show, :id => 1
873 873 assert_response :success
874 874 end
875 875
876 876 def test_show_should_allow_assignee_access_to_private_issue
877 877 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
878 878 @request.session[:user_id] = 3
879 879 get :show, :id => 1
880 880 assert_response :success
881 881 end
882 882
883 883 def test_show_should_allow_member_access_to_private_issue_with_permission
884 884 Issue.update_all(["is_private = ?", true], "id = 1")
885 885 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
886 886 @request.session[:user_id] = 3
887 887 get :show, :id => 1
888 888 assert_response :success
889 889 end
890 890
891 891 def test_show_should_not_disclose_relations_to_invisible_issues
892 892 Setting.cross_project_issue_relations = '1'
893 893 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
894 894 # Relation to a private project issue
895 895 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
896 896
897 897 get :show, :id => 1
898 898 assert_response :success
899 899
900 900 assert_tag :div, :attributes => { :id => 'relations' },
901 901 :descendant => { :tag => 'a', :content => /#2$/ }
902 902 assert_no_tag :div, :attributes => { :id => 'relations' },
903 903 :descendant => { :tag => 'a', :content => /#4$/ }
904 904 end
905 905
906 906 def test_show_should_list_subtasks
907 907 Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
908 908
909 909 get :show, :id => 1
910 910 assert_response :success
911 911 assert_tag 'div', :attributes => {:id => 'issue_tree'},
912 912 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
913 913 end
914 914
915 915 def test_show_should_list_parents
916 916 issue = Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
917 917
918 918 get :show, :id => issue.id
919 919 assert_response :success
920 920 assert_tag 'div', :attributes => {:class => 'subject'},
921 921 :descendant => {:tag => 'h3', :content => 'Child Issue'}
922 922 assert_tag 'div', :attributes => {:class => 'subject'},
923 923 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
924 924 end
925 925
926 926 def test_show_should_not_display_prev_next_links_without_query_in_session
927 927 get :show, :id => 1
928 928 assert_response :success
929 929 assert_nil assigns(:prev_issue_id)
930 930 assert_nil assigns(:next_issue_id)
931 931
932 932 assert_no_tag 'div', :attributes => {:class => /next-prev-links/}
933 933 end
934 934
935 935 def test_show_should_display_prev_next_links_with_query_in_session
936 936 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
937 937 @request.session['issues_index_sort'] = 'id'
938 938
939 939 with_settings :display_subprojects_issues => '0' do
940 940 get :show, :id => 3
941 941 end
942 942
943 943 assert_response :success
944 944 # Previous and next issues for all projects
945 945 assert_equal 2, assigns(:prev_issue_id)
946 946 assert_equal 5, assigns(:next_issue_id)
947 947
948 948 assert_tag 'div', :attributes => {:class => /next-prev-links/}
949 949 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
950 950 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
951 951
952 952 count = Issue.open.visible.count
953 953 assert_tag 'span', :attributes => {:class => 'position'}, :content => "3 of #{count}"
954 954 end
955 955
956 956 def test_show_should_display_prev_next_links_with_saved_query_in_session
957 957 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1,
958 958 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
959 959 :sort_criteria => [['id', 'asc']])
960 960 @request.session[:query] = {:id => query.id, :project_id => nil}
961 961
962 962 get :show, :id => 11
963 963
964 964 assert_response :success
965 965 assert_equal query, assigns(:query)
966 966 # Previous and next issues for all projects
967 967 assert_equal 8, assigns(:prev_issue_id)
968 968 assert_equal 12, assigns(:next_issue_id)
969 969
970 970 assert_tag 'a', :attributes => {:href => '/issues/8'}, :content => /Previous/
971 971 assert_tag 'a', :attributes => {:href => '/issues/12'}, :content => /Next/
972 972 end
973 973
974 974 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
975 975 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
976 976
977 977 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
978 978 @request.session['issues_index_sort'] = assoc_sort
979 979
980 980 get :show, :id => 3
981 981 assert_response :success, "Wrong response status for #{assoc_sort} sort"
982 982
983 983 assert_tag 'a', :content => /Previous/
984 984 assert_tag 'a', :content => /Next/
985 985 end
986 986 end
987 987
988 988 def test_show_should_display_prev_next_links_with_project_query_in_session
989 989 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
990 990 @request.session['issues_index_sort'] = 'id'
991 991
992 992 with_settings :display_subprojects_issues => '0' do
993 993 get :show, :id => 3
994 994 end
995 995
996 996 assert_response :success
997 997 # Previous and next issues inside project
998 998 assert_equal 2, assigns(:prev_issue_id)
999 999 assert_equal 7, assigns(:next_issue_id)
1000 1000
1001 1001 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1002 1002 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
1003 1003 end
1004 1004
1005 1005 def test_show_should_not_display_prev_link_for_first_issue
1006 1006 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1007 1007 @request.session['issues_index_sort'] = 'id'
1008 1008
1009 1009 with_settings :display_subprojects_issues => '0' do
1010 1010 get :show, :id => 1
1011 1011 end
1012 1012
1013 1013 assert_response :success
1014 1014 assert_nil assigns(:prev_issue_id)
1015 1015 assert_equal 2, assigns(:next_issue_id)
1016 1016
1017 1017 assert_no_tag 'a', :content => /Previous/
1018 1018 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
1019 1019 end
1020 1020
1021 1021 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1022 1022 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1023 1023 @request.session['issues_index_sort'] = 'id'
1024 1024
1025 1025 get :show, :id => 1
1026 1026
1027 1027 assert_response :success
1028 1028 assert_nil assigns(:prev_issue_id)
1029 1029 assert_nil assigns(:next_issue_id)
1030 1030
1031 1031 assert_no_tag 'a', :content => /Previous/
1032 1032 assert_no_tag 'a', :content => /Next/
1033 1033 end
1034 1034
1035 1035 def test_show_atom
1036 1036 get :show, :id => 2, :format => 'atom'
1037 1037 assert_response :success
1038 1038 assert_template 'journals/index'
1039 1039 # Inline image
1040 1040 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1041 1041 end
1042 1042
1043 1043 def test_show_export_to_pdf
1044 1044 get :show, :id => 3, :format => 'pdf'
1045 1045 assert_response :success
1046 1046 assert_equal 'application/pdf', @response.content_type
1047 1047 assert @response.body.starts_with?('%PDF')
1048 1048 assert_not_nil assigns(:issue)
1049 1049 end
1050 1050
1051 1051 def test_get_new
1052 1052 @request.session[:user_id] = 2
1053 1053 get :new, :project_id => 1, :tracker_id => 1
1054 1054 assert_response :success
1055 1055 assert_template 'new'
1056 1056
1057 1057 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1058 1058 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1059 1059 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1060 1060 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1061 1061 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1062 1062 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1063 1063 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1064 1064 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1065 1065 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1066 1066 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1067 1067 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1068 1068 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1069 1069 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1070 1070 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1071 1071 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1072 1072 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1073 1073
1074 1074 # Be sure we don't display inactive IssuePriorities
1075 1075 assert ! IssuePriority.find(15).active?
1076 1076 assert_no_tag :option, :attributes => {:value => '15'},
1077 1077 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1078 1078 end
1079 1079
1080 1080 def test_get_new_with_minimal_permissions
1081 1081 Role.find(1).update_attribute :permissions, [:add_issues]
1082 1082 Workflow.delete_all :role_id => 1
1083 1083
1084 1084 @request.session[:user_id] = 2
1085 1085 get :new, :project_id => 1, :tracker_id => 1
1086 1086 assert_response :success
1087 1087 assert_template 'new'
1088 1088
1089 1089 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1090 1090 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1091 1091 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1092 1092 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1093 1093 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1094 1094 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1095 1095 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1096 1096 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1097 1097 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1098 1098 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1099 1099 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1100 1100 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1101 1101 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1102 1102 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1103 1103 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1104 1104 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1105 1105 end
1106 1106
1107 1107 def test_get_new_without_default_start_date_is_creation_date
1108 1108 Setting.default_issue_start_date_to_creation_date = 0
1109 1109
1110 1110 @request.session[:user_id] = 2
1111 1111 get :new, :project_id => 1, :tracker_id => 1
1112 1112 assert_response :success
1113 1113 assert_template 'new'
1114 1114
1115 1115 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1116 1116 :value => nil }
1117 1117 end
1118 1118
1119 1119 def test_get_new_with_default_start_date_is_creation_date
1120 1120 Setting.default_issue_start_date_to_creation_date = 1
1121 1121
1122 1122 @request.session[:user_id] = 2
1123 1123 get :new, :project_id => 1, :tracker_id => 1
1124 1124 assert_response :success
1125 1125 assert_template 'new'
1126 1126
1127 1127 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1128 1128 :value => Date.today.to_s }
1129 1129 end
1130 1130
1131 1131 def test_get_new_form_should_allow_attachment_upload
1132 1132 @request.session[:user_id] = 2
1133 1133 get :new, :project_id => 1, :tracker_id => 1
1134 1134
1135 1135 assert_tag :tag => 'form',
1136 1136 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1137 1137 :descendant => {
1138 1138 :tag => 'input',
1139 1139 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1140 1140 }
1141 1141 end
1142 1142
1143 1143 def test_get_new_without_tracker_id
1144 1144 @request.session[:user_id] = 2
1145 1145 get :new, :project_id => 1
1146 1146 assert_response :success
1147 1147 assert_template 'new'
1148 1148
1149 1149 issue = assigns(:issue)
1150 1150 assert_not_nil issue
1151 1151 assert_equal Project.find(1).trackers.first, issue.tracker
1152 1152 end
1153 1153
1154 1154 def test_get_new_with_no_default_status_should_display_an_error
1155 1155 @request.session[:user_id] = 2
1156 1156 IssueStatus.delete_all
1157 1157
1158 1158 get :new, :project_id => 1
1159 1159 assert_response 500
1160 1160 assert_error_tag :content => /No default issue/
1161 1161 end
1162 1162
1163 1163 def test_get_new_with_no_tracker_should_display_an_error
1164 1164 @request.session[:user_id] = 2
1165 1165 Tracker.delete_all
1166 1166
1167 1167 get :new, :project_id => 1
1168 1168 assert_response 500
1169 1169 assert_error_tag :content => /No tracker/
1170 1170 end
1171 1171
1172 1172 def test_update_new_form
1173 1173 @request.session[:user_id] = 2
1174 1174 xhr :post, :new, :project_id => 1,
1175 1175 :issue => {:tracker_id => 2,
1176 1176 :subject => 'This is the test_new issue',
1177 1177 :description => 'This is the description',
1178 1178 :priority_id => 5}
1179 1179 assert_response :success
1180 1180 assert_template 'attributes'
1181 1181
1182 1182 issue = assigns(:issue)
1183 1183 assert_kind_of Issue, issue
1184 1184 assert_equal 1, issue.project_id
1185 1185 assert_equal 2, issue.tracker_id
1186 1186 assert_equal 'This is the test_new issue', issue.subject
1187 1187 end
1188 1188
1189 1189 def test_post_create
1190 1190 @request.session[:user_id] = 2
1191 1191 assert_difference 'Issue.count' do
1192 1192 post :create, :project_id => 1,
1193 1193 :issue => {:tracker_id => 3,
1194 1194 :status_id => 2,
1195 1195 :subject => 'This is the test_new issue',
1196 1196 :description => 'This is the description',
1197 1197 :priority_id => 5,
1198 1198 :start_date => '2010-11-07',
1199 1199 :estimated_hours => '',
1200 1200 :custom_field_values => {'2' => 'Value for field 2'}}
1201 1201 end
1202 1202 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1203 1203
1204 1204 issue = Issue.find_by_subject('This is the test_new issue')
1205 1205 assert_not_nil issue
1206 1206 assert_equal 2, issue.author_id
1207 1207 assert_equal 3, issue.tracker_id
1208 1208 assert_equal 2, issue.status_id
1209 1209 assert_equal Date.parse('2010-11-07'), issue.start_date
1210 1210 assert_nil issue.estimated_hours
1211 1211 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1212 1212 assert_not_nil v
1213 1213 assert_equal 'Value for field 2', v.value
1214 1214 end
1215 1215
1216 1216 def test_post_new_with_group_assignment
1217 1217 group = Group.find(11)
1218 1218 project = Project.find(1)
1219 1219 project.members << Member.new(:principal => group, :roles => [Role.first])
1220 1220
1221 1221 with_settings :issue_group_assignment => '1' do
1222 1222 @request.session[:user_id] = 2
1223 1223 assert_difference 'Issue.count' do
1224 1224 post :create, :project_id => project.id,
1225 1225 :issue => {:tracker_id => 3,
1226 1226 :status_id => 1,
1227 1227 :subject => 'This is the test_new_with_group_assignment issue',
1228 1228 :assigned_to_id => group.id}
1229 1229 end
1230 1230 end
1231 1231 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1232 1232
1233 1233 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1234 1234 assert_not_nil issue
1235 1235 assert_equal group, issue.assigned_to
1236 1236 end
1237 1237
1238 1238 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1239 1239 Setting.default_issue_start_date_to_creation_date = 0
1240 1240
1241 1241 @request.session[:user_id] = 2
1242 1242 assert_difference 'Issue.count' do
1243 1243 post :create, :project_id => 1,
1244 1244 :issue => {:tracker_id => 3,
1245 1245 :status_id => 2,
1246 1246 :subject => 'This is the test_new issue',
1247 1247 :description => 'This is the description',
1248 1248 :priority_id => 5,
1249 1249 :estimated_hours => '',
1250 1250 :custom_field_values => {'2' => 'Value for field 2'}}
1251 1251 end
1252 1252 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1253 1253
1254 1254 issue = Issue.find_by_subject('This is the test_new issue')
1255 1255 assert_not_nil issue
1256 1256 assert_nil issue.start_date
1257 1257 end
1258 1258
1259 1259 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1260 1260 Setting.default_issue_start_date_to_creation_date = 1
1261 1261
1262 1262 @request.session[:user_id] = 2
1263 1263 assert_difference 'Issue.count' do
1264 1264 post :create, :project_id => 1,
1265 1265 :issue => {:tracker_id => 3,
1266 1266 :status_id => 2,
1267 1267 :subject => 'This is the test_new issue',
1268 1268 :description => 'This is the description',
1269 1269 :priority_id => 5,
1270 1270 :estimated_hours => '',
1271 1271 :custom_field_values => {'2' => 'Value for field 2'}}
1272 1272 end
1273 1273 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1274 1274
1275 1275 issue = Issue.find_by_subject('This is the test_new issue')
1276 1276 assert_not_nil issue
1277 1277 assert_equal Date.today, issue.start_date
1278 1278 end
1279 1279
1280 1280 def test_post_create_and_continue
1281 1281 @request.session[:user_id] = 2
1282 1282 assert_difference 'Issue.count' do
1283 1283 post :create, :project_id => 1,
1284 1284 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1285 1285 :continue => ''
1286 1286 end
1287 1287
1288 1288 issue = Issue.first(:order => 'id DESC')
1289 1289 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1290 1290 assert_not_nil flash[:notice], "flash was not set"
1291 1291 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1292 1292 end
1293 1293
1294 1294 def test_post_create_without_custom_fields_param
1295 1295 @request.session[:user_id] = 2
1296 1296 assert_difference 'Issue.count' do
1297 1297 post :create, :project_id => 1,
1298 1298 :issue => {:tracker_id => 1,
1299 1299 :subject => 'This is the test_new issue',
1300 1300 :description => 'This is the description',
1301 1301 :priority_id => 5}
1302 1302 end
1303 1303 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1304 1304 end
1305 1305
1306 1306 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1307 1307 field = IssueCustomField.find_by_name('Database')
1308 1308 field.update_attribute(:is_required, true)
1309 1309
1310 1310 @request.session[:user_id] = 2
1311 assert_no_difference 'Issue.count' do
1311 1312 post :create, :project_id => 1,
1312 1313 :issue => {:tracker_id => 1,
1313 1314 :subject => 'This is the test_new issue',
1314 1315 :description => 'This is the description',
1315 1316 :priority_id => 5}
1317 end
1316 1318 assert_response :success
1317 1319 assert_template 'new'
1318 1320 issue = assigns(:issue)
1319 1321 assert_not_nil issue
1320 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1321 issue.errors[:custom_values].to_s
1322 assert_error_tag :content => /Database can't be blank/
1322 1323 end
1323 1324
1324 1325 def test_post_create_with_watchers
1325 1326 @request.session[:user_id] = 2
1326 1327 ActionMailer::Base.deliveries.clear
1327 1328
1328 1329 assert_difference 'Watcher.count', 2 do
1329 1330 post :create, :project_id => 1,
1330 1331 :issue => {:tracker_id => 1,
1331 1332 :subject => 'This is a new issue with watchers',
1332 1333 :description => 'This is the description',
1333 1334 :priority_id => 5,
1334 1335 :watcher_user_ids => ['2', '3']}
1335 1336 end
1336 1337 issue = Issue.find_by_subject('This is a new issue with watchers')
1337 1338 assert_not_nil issue
1338 1339 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1339 1340
1340 1341 # Watchers added
1341 1342 assert_equal [2, 3], issue.watcher_user_ids.sort
1342 1343 assert issue.watched_by?(User.find(3))
1343 1344 # Watchers notified
1344 1345 mail = ActionMailer::Base.deliveries.last
1345 1346 assert_kind_of TMail::Mail, mail
1346 1347 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1347 1348 end
1348 1349
1349 1350 def test_post_create_subissue
1350 1351 @request.session[:user_id] = 2
1351 1352
1352 1353 assert_difference 'Issue.count' do
1353 1354 post :create, :project_id => 1,
1354 1355 :issue => {:tracker_id => 1,
1355 1356 :subject => 'This is a child issue',
1356 1357 :parent_issue_id => 2}
1357 1358 end
1358 1359 issue = Issue.find_by_subject('This is a child issue')
1359 1360 assert_not_nil issue
1360 1361 assert_equal Issue.find(2), issue.parent
1361 1362 end
1362 1363
1363 1364 def test_post_create_subissue_with_non_numeric_parent_id
1364 1365 @request.session[:user_id] = 2
1365 1366
1366 1367 assert_difference 'Issue.count' do
1367 1368 post :create, :project_id => 1,
1368 1369 :issue => {:tracker_id => 1,
1369 1370 :subject => 'This is a child issue',
1370 1371 :parent_issue_id => 'ABC'}
1371 1372 end
1372 1373 issue = Issue.find_by_subject('This is a child issue')
1373 1374 assert_not_nil issue
1374 1375 assert_nil issue.parent
1375 1376 end
1376 1377
1377 1378 def test_post_create_private
1378 1379 @request.session[:user_id] = 2
1379 1380
1380 1381 assert_difference 'Issue.count' do
1381 1382 post :create, :project_id => 1,
1382 1383 :issue => {:tracker_id => 1,
1383 1384 :subject => 'This is a private issue',
1384 1385 :is_private => '1'}
1385 1386 end
1386 1387 issue = Issue.first(:order => 'id DESC')
1387 1388 assert issue.is_private?
1388 1389 end
1389 1390
1390 1391 def test_post_create_private_with_set_own_issues_private_permission
1391 1392 role = Role.find(1)
1392 1393 role.remove_permission! :set_issues_private
1393 1394 role.add_permission! :set_own_issues_private
1394 1395
1395 1396 @request.session[:user_id] = 2
1396 1397
1397 1398 assert_difference 'Issue.count' do
1398 1399 post :create, :project_id => 1,
1399 1400 :issue => {:tracker_id => 1,
1400 1401 :subject => 'This is a private issue',
1401 1402 :is_private => '1'}
1402 1403 end
1403 1404 issue = Issue.first(:order => 'id DESC')
1404 1405 assert issue.is_private?
1405 1406 end
1406 1407
1407 1408 def test_post_create_should_send_a_notification
1408 1409 ActionMailer::Base.deliveries.clear
1409 1410 @request.session[:user_id] = 2
1410 1411 assert_difference 'Issue.count' do
1411 1412 post :create, :project_id => 1,
1412 1413 :issue => {:tracker_id => 3,
1413 1414 :subject => 'This is the test_new issue',
1414 1415 :description => 'This is the description',
1415 1416 :priority_id => 5,
1416 1417 :estimated_hours => '',
1417 1418 :custom_field_values => {'2' => 'Value for field 2'}}
1418 1419 end
1419 1420 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1420 1421
1421 1422 assert_equal 1, ActionMailer::Base.deliveries.size
1422 1423 end
1423 1424
1424 1425 def test_post_create_should_preserve_fields_values_on_validation_failure
1425 1426 @request.session[:user_id] = 2
1426 1427 post :create, :project_id => 1,
1427 1428 :issue => {:tracker_id => 1,
1428 1429 # empty subject
1429 1430 :subject => '',
1430 1431 :description => 'This is a description',
1431 1432 :priority_id => 6,
1432 1433 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1433 1434 assert_response :success
1434 1435 assert_template 'new'
1435 1436
1436 1437 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1437 1438 :content => 'This is a description'
1438 1439 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1439 1440 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1440 1441 :value => '6' },
1441 1442 :content => 'High' }
1442 1443 # Custom fields
1443 1444 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1444 1445 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1445 1446 :value => 'Oracle' },
1446 1447 :content => 'Oracle' }
1447 1448 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1448 1449 :value => 'Value for field 2'}
1449 1450 end
1450 1451
1451 1452 def test_post_create_should_ignore_non_safe_attributes
1452 1453 @request.session[:user_id] = 2
1453 1454 assert_nothing_raised do
1454 1455 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1455 1456 end
1456 1457 end
1457 1458
1458 1459 def test_post_create_with_attachment
1459 1460 set_tmp_attachments_directory
1460 1461 @request.session[:user_id] = 2
1461 1462
1462 1463 assert_difference 'Issue.count' do
1463 1464 assert_difference 'Attachment.count' do
1464 1465 post :create, :project_id => 1,
1465 1466 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1466 1467 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1467 1468 end
1468 1469 end
1469 1470
1470 1471 issue = Issue.first(:order => 'id DESC')
1471 1472 attachment = Attachment.first(:order => 'id DESC')
1472 1473
1473 1474 assert_equal issue, attachment.container
1474 1475 assert_equal 2, attachment.author_id
1475 1476 assert_equal 'testfile.txt', attachment.filename
1476 1477 assert_equal 'text/plain', attachment.content_type
1477 1478 assert_equal 'test file', attachment.description
1478 1479 assert_equal 59, attachment.filesize
1479 1480 assert File.exists?(attachment.diskfile)
1480 1481 assert_equal 59, File.size(attachment.diskfile)
1481 1482 end
1482 1483
1483 1484 context "without workflow privilege" do
1484 1485 setup do
1485 1486 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1486 1487 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1487 1488 end
1488 1489
1489 1490 context "#new" do
1490 1491 should "propose default status only" do
1491 1492 get :new, :project_id => 1
1492 1493 assert_response :success
1493 1494 assert_template 'new'
1494 1495 assert_tag :tag => 'select',
1495 1496 :attributes => {:name => 'issue[status_id]'},
1496 1497 :children => {:count => 1},
1497 1498 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1498 1499 end
1499 1500
1500 1501 should "accept default status" do
1501 1502 assert_difference 'Issue.count' do
1502 1503 post :create, :project_id => 1,
1503 1504 :issue => {:tracker_id => 1,
1504 1505 :subject => 'This is an issue',
1505 1506 :status_id => 1}
1506 1507 end
1507 1508 issue = Issue.last(:order => 'id')
1508 1509 assert_equal IssueStatus.default, issue.status
1509 1510 end
1510 1511
1511 1512 should "ignore unauthorized status" do
1512 1513 assert_difference 'Issue.count' do
1513 1514 post :create, :project_id => 1,
1514 1515 :issue => {:tracker_id => 1,
1515 1516 :subject => 'This is an issue',
1516 1517 :status_id => 3}
1517 1518 end
1518 1519 issue = Issue.last(:order => 'id')
1519 1520 assert_equal IssueStatus.default, issue.status
1520 1521 end
1521 1522 end
1522 1523
1523 1524 context "#update" do
1524 1525 should "ignore status change" do
1525 1526 assert_difference 'Journal.count' do
1526 1527 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1527 1528 end
1528 1529 assert_equal 1, Issue.find(1).status_id
1529 1530 end
1530 1531
1531 1532 should "ignore attributes changes" do
1532 1533 assert_difference 'Journal.count' do
1533 1534 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1534 1535 end
1535 1536 issue = Issue.find(1)
1536 1537 assert_equal "Can't print recipes", issue.subject
1537 1538 assert_nil issue.assigned_to
1538 1539 end
1539 1540 end
1540 1541 end
1541 1542
1542 1543 context "with workflow privilege" do
1543 1544 setup do
1544 1545 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1545 1546 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1546 1547 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1547 1548 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1548 1549 end
1549 1550
1550 1551 context "#update" do
1551 1552 should "accept authorized status" do
1552 1553 assert_difference 'Journal.count' do
1553 1554 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1554 1555 end
1555 1556 assert_equal 3, Issue.find(1).status_id
1556 1557 end
1557 1558
1558 1559 should "ignore unauthorized status" do
1559 1560 assert_difference 'Journal.count' do
1560 1561 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1561 1562 end
1562 1563 assert_equal 1, Issue.find(1).status_id
1563 1564 end
1564 1565
1565 1566 should "accept authorized attributes changes" do
1566 1567 assert_difference 'Journal.count' do
1567 1568 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1568 1569 end
1569 1570 issue = Issue.find(1)
1570 1571 assert_equal 2, issue.assigned_to_id
1571 1572 end
1572 1573
1573 1574 should "ignore unauthorized attributes changes" do
1574 1575 assert_difference 'Journal.count' do
1575 1576 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1576 1577 end
1577 1578 issue = Issue.find(1)
1578 1579 assert_equal "Can't print recipes", issue.subject
1579 1580 end
1580 1581 end
1581 1582
1582 1583 context "and :edit_issues permission" do
1583 1584 setup do
1584 1585 Role.anonymous.add_permission! :add_issues, :edit_issues
1585 1586 end
1586 1587
1587 1588 should "accept authorized status" do
1588 1589 assert_difference 'Journal.count' do
1589 1590 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1590 1591 end
1591 1592 assert_equal 3, Issue.find(1).status_id
1592 1593 end
1593 1594
1594 1595 should "ignore unauthorized status" do
1595 1596 assert_difference 'Journal.count' do
1596 1597 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1597 1598 end
1598 1599 assert_equal 1, Issue.find(1).status_id
1599 1600 end
1600 1601
1601 1602 should "accept authorized attributes changes" do
1602 1603 assert_difference 'Journal.count' do
1603 1604 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1604 1605 end
1605 1606 issue = Issue.find(1)
1606 1607 assert_equal "changed", issue.subject
1607 1608 assert_equal 2, issue.assigned_to_id
1608 1609 end
1609 1610 end
1610 1611 end
1611 1612
1612 1613 def test_new_as_copy
1613 1614 @request.session[:user_id] = 2
1614 1615 get :new, :project_id => 1, :copy_from => 1
1615 1616
1616 1617 assert_response :success
1617 1618 assert_template 'new'
1618 1619
1619 1620 assert_not_nil assigns(:issue)
1620 1621 orig = Issue.find(1)
1621 1622 assert_equal 1, assigns(:issue).project_id
1622 1623 assert_equal orig.subject, assigns(:issue).subject
1623 1624 assert assigns(:issue).copy?
1624 1625
1625 1626 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
1626 1627 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
1627 1628 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1628 1629 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'}
1629 1630 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1630 1631 :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'}
1631 1632 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
1632 1633 end
1633 1634
1634 1635 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
1635 1636 @request.session[:user_id] = 2
1636 1637 issue = Issue.find(3)
1637 1638 assert issue.attachments.count > 0
1638 1639 get :new, :project_id => 1, :copy_from => 3
1639 1640
1640 1641 assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1641 1642 end
1642 1643
1643 1644 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
1644 1645 @request.session[:user_id] = 2
1645 1646 issue = Issue.find(3)
1646 1647 issue.attachments.delete_all
1647 1648 get :new, :project_id => 1, :copy_from => 3
1648 1649
1649 1650 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1650 1651 end
1651 1652
1652 1653 def test_new_as_copy_with_invalid_issue_should_respond_with_404
1653 1654 @request.session[:user_id] = 2
1654 1655 get :new, :project_id => 1, :copy_from => 99999
1655 1656 assert_response 404
1656 1657 end
1657 1658
1658 1659 def test_create_as_copy_on_different_project
1659 1660 @request.session[:user_id] = 2
1660 1661 assert_difference 'Issue.count' do
1661 1662 post :create, :project_id => 1, :copy_from => 1,
1662 1663 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
1663 1664
1664 1665 assert_not_nil assigns(:issue)
1665 1666 assert assigns(:issue).copy?
1666 1667 end
1667 1668 issue = Issue.first(:order => 'id DESC')
1668 1669 assert_redirected_to "/issues/#{issue.id}"
1669 1670
1670 1671 assert_equal 2, issue.project_id
1671 1672 assert_equal 3, issue.tracker_id
1672 1673 assert_equal 'Copy', issue.subject
1673 1674 end
1674 1675
1675 1676 def test_create_as_copy_should_copy_attachments
1676 1677 @request.session[:user_id] = 2
1677 1678 issue = Issue.find(3)
1678 1679 count = issue.attachments.count
1679 1680 assert count > 0
1680 1681
1681 1682 assert_difference 'Issue.count' do
1682 1683 assert_difference 'Attachment.count', count do
1683 1684 assert_no_difference 'Journal.count' do
1684 1685 post :create, :project_id => 1, :copy_from => 3,
1685 1686 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
1686 1687 :copy_attachments => '1'
1687 1688 end
1688 1689 end
1689 1690 end
1690 1691 copy = Issue.first(:order => 'id DESC')
1691 1692 assert_equal count, copy.attachments.count
1692 1693 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
1693 1694 end
1694 1695
1695 1696 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
1696 1697 @request.session[:user_id] = 2
1697 1698 issue = Issue.find(3)
1698 1699 count = issue.attachments.count
1699 1700 assert count > 0
1700 1701
1701 1702 assert_difference 'Issue.count' do
1702 1703 assert_no_difference 'Attachment.count' do
1703 1704 assert_no_difference 'Journal.count' do
1704 1705 post :create, :project_id => 1, :copy_from => 3,
1705 1706 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
1706 1707 end
1707 1708 end
1708 1709 end
1709 1710 copy = Issue.first(:order => 'id DESC')
1710 1711 assert_equal 0, copy.attachments.count
1711 1712 end
1712 1713
1713 1714 def test_create_as_copy_with_attachments_should_add_new_files
1714 1715 @request.session[:user_id] = 2
1715 1716 issue = Issue.find(3)
1716 1717 count = issue.attachments.count
1717 1718 assert count > 0
1718 1719
1719 1720 assert_difference 'Issue.count' do
1720 1721 assert_difference 'Attachment.count', count + 1 do
1721 1722 assert_no_difference 'Journal.count' do
1722 1723 post :create, :project_id => 1, :copy_from => 3,
1723 1724 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
1724 1725 :copy_attachments => '1',
1725 1726 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1726 1727 end
1727 1728 end
1728 1729 end
1729 1730 copy = Issue.first(:order => 'id DESC')
1730 1731 assert_equal count + 1, copy.attachments.count
1731 1732 end
1732 1733
1733 1734 def test_create_as_copy_with_failure
1734 1735 @request.session[:user_id] = 2
1735 1736 post :create, :project_id => 1, :copy_from => 1,
1736 1737 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
1737 1738
1738 1739 assert_response :success
1739 1740 assert_template 'new'
1740 1741
1741 1742 assert_not_nil assigns(:issue)
1742 1743 assert assigns(:issue).copy?
1743 1744
1744 1745 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
1745 1746 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
1746 1747 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1747 1748 :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'}
1748 1749 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1749 1750 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'}
1750 1751 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
1751 1752 end
1752 1753
1753 1754 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
1754 1755 @request.session[:user_id] = 2
1755 1756 assert !User.find(2).member_of?(Project.find(4))
1756 1757
1757 1758 assert_difference 'Issue.count' do
1758 1759 post :create, :project_id => 1, :copy_from => 1,
1759 1760 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
1760 1761 end
1761 1762 issue = Issue.first(:order => 'id DESC')
1762 1763 assert_equal 1, issue.project_id
1763 1764 end
1764 1765
1765 1766 def test_get_edit
1766 1767 @request.session[:user_id] = 2
1767 1768 get :edit, :id => 1
1768 1769 assert_response :success
1769 1770 assert_template 'edit'
1770 1771 assert_not_nil assigns(:issue)
1771 1772 assert_equal Issue.find(1), assigns(:issue)
1772 1773
1773 1774 # Be sure we don't display inactive IssuePriorities
1774 1775 assert ! IssuePriority.find(15).active?
1775 1776 assert_no_tag :option, :attributes => {:value => '15'},
1776 1777 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1777 1778 end
1778 1779
1779 1780 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
1780 1781 @request.session[:user_id] = 2
1781 1782 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
1782 1783
1783 1784 get :edit, :id => 1
1784 1785 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1785 1786 end
1786 1787
1787 1788 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
1788 1789 @request.session[:user_id] = 2
1789 1790 Role.find_by_name('Manager').remove_permission! :log_time
1790 1791
1791 1792 get :edit, :id => 1
1792 1793 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1793 1794 end
1794 1795
1795 1796 def test_get_edit_with_params
1796 1797 @request.session[:user_id] = 2
1797 1798 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1798 1799 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1799 1800 assert_response :success
1800 1801 assert_template 'edit'
1801 1802
1802 1803 issue = assigns(:issue)
1803 1804 assert_not_nil issue
1804 1805
1805 1806 assert_equal 5, issue.status_id
1806 1807 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1807 1808 :child => { :tag => 'option',
1808 1809 :content => 'Closed',
1809 1810 :attributes => { :selected => 'selected' } }
1810 1811
1811 1812 assert_equal 7, issue.priority_id
1812 1813 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1813 1814 :child => { :tag => 'option',
1814 1815 :content => 'Urgent',
1815 1816 :attributes => { :selected => 'selected' } }
1816 1817
1817 1818 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1818 1819 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1819 1820 :child => { :tag => 'option',
1820 1821 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1821 1822 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1822 1823 end
1823 1824
1824 1825 def test_update_edit_form
1825 1826 @request.session[:user_id] = 2
1826 1827 xhr :put, :new, :project_id => 1,
1827 1828 :id => 1,
1828 1829 :issue => {:tracker_id => 2,
1829 1830 :subject => 'This is the test_new issue',
1830 1831 :description => 'This is the description',
1831 1832 :priority_id => 5}
1832 1833 assert_response :success
1833 1834 assert_template 'attributes'
1834 1835
1835 1836 issue = assigns(:issue)
1836 1837 assert_kind_of Issue, issue
1837 1838 assert_equal 1, issue.id
1838 1839 assert_equal 1, issue.project_id
1839 1840 assert_equal 2, issue.tracker_id
1840 1841 assert_equal 'This is the test_new issue', issue.subject
1841 1842 end
1842 1843
1843 1844 def test_update_edit_form_with_project_change
1844 1845 @request.session[:user_id] = 2
1845 1846 xhr :put, :new, :project_id => 1,
1846 1847 :id => 1,
1847 1848 :project_change => '1',
1848 1849 :issue => {:project_id => 2,
1849 1850 :tracker_id => 2,
1850 1851 :subject => 'This is the test_new issue',
1851 1852 :description => 'This is the description',
1852 1853 :priority_id => 5}
1853 1854 assert_response :success
1854 1855 assert_template 'form'
1855 1856
1856 1857 issue = assigns(:issue)
1857 1858 assert_kind_of Issue, issue
1858 1859 assert_equal 1, issue.id
1859 1860 assert_equal 2, issue.project_id
1860 1861 assert_equal 2, issue.tracker_id
1861 1862 assert_equal 'This is the test_new issue', issue.subject
1862 1863 end
1863 1864
1864 1865 def test_update_using_invalid_http_verbs
1865 1866 @request.session[:user_id] = 2
1866 1867 subject = 'Updated by an invalid http verb'
1867 1868
1868 1869 get :update, :id => 1, :issue => {:subject => subject}
1869 1870 assert_not_equal subject, Issue.find(1).subject
1870 1871
1871 1872 post :update, :id => 1, :issue => {:subject => subject}
1872 1873 assert_not_equal subject, Issue.find(1).subject
1873 1874
1874 1875 delete :update, :id => 1, :issue => {:subject => subject}
1875 1876 assert_not_equal subject, Issue.find(1).subject
1876 1877 end
1877 1878
1878 1879 def test_put_update_without_custom_fields_param
1879 1880 @request.session[:user_id] = 2
1880 1881 ActionMailer::Base.deliveries.clear
1881 1882
1882 1883 issue = Issue.find(1)
1883 1884 assert_equal '125', issue.custom_value_for(2).value
1884 1885 old_subject = issue.subject
1885 1886 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1886 1887
1887 1888 assert_difference('Journal.count') do
1888 1889 assert_difference('JournalDetail.count', 2) do
1889 1890 put :update, :id => 1, :issue => {:subject => new_subject,
1890 1891 :priority_id => '6',
1891 1892 :category_id => '1' # no change
1892 1893 }
1893 1894 end
1894 1895 end
1895 1896 assert_redirected_to :action => 'show', :id => '1'
1896 1897 issue.reload
1897 1898 assert_equal new_subject, issue.subject
1898 1899 # Make sure custom fields were not cleared
1899 1900 assert_equal '125', issue.custom_value_for(2).value
1900 1901
1901 1902 mail = ActionMailer::Base.deliveries.last
1902 1903 assert_kind_of TMail::Mail, mail
1903 1904 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1904 1905 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1905 1906 end
1906 1907
1907 1908 def test_put_update_with_project_change
1908 1909 @request.session[:user_id] = 2
1909 1910 ActionMailer::Base.deliveries.clear
1910 1911
1911 1912 assert_difference('Journal.count') do
1912 1913 assert_difference('JournalDetail.count', 3) do
1913 1914 put :update, :id => 1, :issue => {:project_id => '2',
1914 1915 :tracker_id => '1', # no change
1915 1916 :priority_id => '6',
1916 1917 :category_id => '3'
1917 1918 }
1918 1919 end
1919 1920 end
1920 1921 assert_redirected_to :action => 'show', :id => '1'
1921 1922 issue = Issue.find(1)
1922 1923 assert_equal 2, issue.project_id
1923 1924 assert_equal 1, issue.tracker_id
1924 1925 assert_equal 6, issue.priority_id
1925 1926 assert_equal 3, issue.category_id
1926 1927
1927 1928 mail = ActionMailer::Base.deliveries.last
1928 1929 assert_not_nil mail
1929 1930 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1930 1931 assert mail.body.include?("Project changed from eCookbook to OnlineStore")
1931 1932 end
1932 1933
1933 1934 def test_put_update_with_tracker_change
1934 1935 @request.session[:user_id] = 2
1935 1936 ActionMailer::Base.deliveries.clear
1936 1937
1937 1938 assert_difference('Journal.count') do
1938 1939 assert_difference('JournalDetail.count', 2) do
1939 1940 put :update, :id => 1, :issue => {:project_id => '1',
1940 1941 :tracker_id => '2',
1941 1942 :priority_id => '6'
1942 1943 }
1943 1944 end
1944 1945 end
1945 1946 assert_redirected_to :action => 'show', :id => '1'
1946 1947 issue = Issue.find(1)
1947 1948 assert_equal 1, issue.project_id
1948 1949 assert_equal 2, issue.tracker_id
1949 1950 assert_equal 6, issue.priority_id
1950 1951 assert_equal 1, issue.category_id
1951 1952
1952 1953 mail = ActionMailer::Base.deliveries.last
1953 1954 assert_not_nil mail
1954 1955 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1955 1956 assert mail.body.include?("Tracker changed from Bug to Feature request")
1956 1957 end
1957 1958
1958 1959 def test_put_update_with_custom_field_change
1959 1960 @request.session[:user_id] = 2
1960 1961 issue = Issue.find(1)
1961 1962 assert_equal '125', issue.custom_value_for(2).value
1962 1963
1963 1964 assert_difference('Journal.count') do
1964 1965 assert_difference('JournalDetail.count', 3) do
1965 1966 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1966 1967 :priority_id => '6',
1967 1968 :category_id => '1', # no change
1968 1969 :custom_field_values => { '2' => 'New custom value' }
1969 1970 }
1970 1971 end
1971 1972 end
1972 1973 assert_redirected_to :action => 'show', :id => '1'
1973 1974 issue.reload
1974 1975 assert_equal 'New custom value', issue.custom_value_for(2).value
1975 1976
1976 1977 mail = ActionMailer::Base.deliveries.last
1977 1978 assert_kind_of TMail::Mail, mail
1978 1979 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1979 1980 end
1980 1981
1981 1982 def test_put_update_with_status_and_assignee_change
1982 1983 issue = Issue.find(1)
1983 1984 assert_equal 1, issue.status_id
1984 1985 @request.session[:user_id] = 2
1985 1986 assert_difference('TimeEntry.count', 0) do
1986 1987 put :update,
1987 1988 :id => 1,
1988 1989 :issue => { :status_id => 2, :assigned_to_id => 3 },
1989 1990 :notes => 'Assigned to dlopper',
1990 1991 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1991 1992 end
1992 1993 assert_redirected_to :action => 'show', :id => '1'
1993 1994 issue.reload
1994 1995 assert_equal 2, issue.status_id
1995 1996 j = Journal.find(:first, :order => 'id DESC')
1996 1997 assert_equal 'Assigned to dlopper', j.notes
1997 1998 assert_equal 2, j.details.size
1998 1999
1999 2000 mail = ActionMailer::Base.deliveries.last
2000 2001 assert mail.body.include?("Status changed from New to Assigned")
2001 2002 # subject should contain the new status
2002 2003 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2003 2004 end
2004 2005
2005 2006 def test_put_update_with_note_only
2006 2007 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2007 2008 # anonymous user
2008 2009 put :update,
2009 2010 :id => 1,
2010 2011 :notes => notes
2011 2012 assert_redirected_to :action => 'show', :id => '1'
2012 2013 j = Journal.find(:first, :order => 'id DESC')
2013 2014 assert_equal notes, j.notes
2014 2015 assert_equal 0, j.details.size
2015 2016 assert_equal User.anonymous, j.user
2016 2017
2017 2018 mail = ActionMailer::Base.deliveries.last
2018 2019 assert mail.body.include?(notes)
2019 2020 end
2020 2021
2021 2022 def test_put_update_with_note_and_spent_time
2022 2023 @request.session[:user_id] = 2
2023 2024 spent_hours_before = Issue.find(1).spent_hours
2024 2025 assert_difference('TimeEntry.count') do
2025 2026 put :update,
2026 2027 :id => 1,
2027 2028 :notes => '2.5 hours added',
2028 2029 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2029 2030 end
2030 2031 assert_redirected_to :action => 'show', :id => '1'
2031 2032
2032 2033 issue = Issue.find(1)
2033 2034
2034 2035 j = Journal.find(:first, :order => 'id DESC')
2035 2036 assert_equal '2.5 hours added', j.notes
2036 2037 assert_equal 0, j.details.size
2037 2038
2038 2039 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2039 2040 assert_not_nil t
2040 2041 assert_equal 2.5, t.hours
2041 2042 assert_equal spent_hours_before + 2.5, issue.spent_hours
2042 2043 end
2043 2044
2044 2045 def test_put_update_with_attachment_only
2045 2046 set_tmp_attachments_directory
2046 2047
2047 2048 # Delete all fixtured journals, a race condition can occur causing the wrong
2048 2049 # journal to get fetched in the next find.
2049 2050 Journal.delete_all
2050 2051
2051 2052 # anonymous user
2052 2053 assert_difference 'Attachment.count' do
2053 2054 put :update, :id => 1,
2054 2055 :notes => '',
2055 2056 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2056 2057 end
2057 2058
2058 2059 assert_redirected_to :action => 'show', :id => '1'
2059 2060 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
2060 2061 assert j.notes.blank?
2061 2062 assert_equal 1, j.details.size
2062 2063 assert_equal 'testfile.txt', j.details.first.value
2063 2064 assert_equal User.anonymous, j.user
2064 2065
2065 2066 attachment = Attachment.first(:order => 'id DESC')
2066 2067 assert_equal Issue.find(1), attachment.container
2067 2068 assert_equal User.anonymous, attachment.author
2068 2069 assert_equal 'testfile.txt', attachment.filename
2069 2070 assert_equal 'text/plain', attachment.content_type
2070 2071 assert_equal 'test file', attachment.description
2071 2072 assert_equal 59, attachment.filesize
2072 2073 assert File.exists?(attachment.diskfile)
2073 2074 assert_equal 59, File.size(attachment.diskfile)
2074 2075
2075 2076 mail = ActionMailer::Base.deliveries.last
2076 2077 assert mail.body.include?('testfile.txt')
2077 2078 end
2078 2079
2079 2080 def test_put_update_with_attachment_that_fails_to_save
2080 2081 set_tmp_attachments_directory
2081 2082
2082 2083 # Delete all fixtured journals, a race condition can occur causing the wrong
2083 2084 # journal to get fetched in the next find.
2084 2085 Journal.delete_all
2085 2086
2086 2087 # Mock out the unsaved attachment
2087 2088 Attachment.any_instance.stubs(:create).returns(Attachment.new)
2088 2089
2089 2090 # anonymous user
2090 2091 put :update,
2091 2092 :id => 1,
2092 2093 :notes => '',
2093 2094 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
2094 2095 assert_redirected_to :action => 'show', :id => '1'
2095 2096 assert_equal '1 file(s) could not be saved.', flash[:warning]
2096 2097
2097 2098 end if Object.const_defined?(:Mocha)
2098 2099
2099 2100 def test_put_update_with_no_change
2100 2101 issue = Issue.find(1)
2101 2102 issue.journals.clear
2102 2103 ActionMailer::Base.deliveries.clear
2103 2104
2104 2105 put :update,
2105 2106 :id => 1,
2106 2107 :notes => ''
2107 2108 assert_redirected_to :action => 'show', :id => '1'
2108 2109
2109 2110 issue.reload
2110 2111 assert issue.journals.empty?
2111 2112 # No email should be sent
2112 2113 assert ActionMailer::Base.deliveries.empty?
2113 2114 end
2114 2115
2115 2116 def test_put_update_should_send_a_notification
2116 2117 @request.session[:user_id] = 2
2117 2118 ActionMailer::Base.deliveries.clear
2118 2119 issue = Issue.find(1)
2119 2120 old_subject = issue.subject
2120 2121 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2121 2122
2122 2123 put :update, :id => 1, :issue => {:subject => new_subject,
2123 2124 :priority_id => '6',
2124 2125 :category_id => '1' # no change
2125 2126 }
2126 2127 assert_equal 1, ActionMailer::Base.deliveries.size
2127 2128 end
2128 2129
2129 2130 def test_put_update_with_invalid_spent_time_hours_only
2130 2131 @request.session[:user_id] = 2
2131 2132 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2132 2133
2133 2134 assert_no_difference('Journal.count') do
2134 2135 put :update,
2135 2136 :id => 1,
2136 2137 :notes => notes,
2137 2138 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
2138 2139 end
2139 2140 assert_response :success
2140 2141 assert_template 'edit'
2141 2142
2142 2143 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2143 2144 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2144 2145 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
2145 2146 end
2146 2147
2147 2148 def test_put_update_with_invalid_spent_time_comments_only
2148 2149 @request.session[:user_id] = 2
2149 2150 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2150 2151
2151 2152 assert_no_difference('Journal.count') do
2152 2153 put :update,
2153 2154 :id => 1,
2154 2155 :notes => notes,
2155 2156 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
2156 2157 end
2157 2158 assert_response :success
2158 2159 assert_template 'edit'
2159 2160
2160 2161 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2161 2162 assert_error_tag :descendant => {:content => /Hours can't be blank/}
2162 2163 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2163 2164 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
2164 2165 end
2165 2166
2166 2167 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
2167 2168 issue = Issue.find(2)
2168 2169 @request.session[:user_id] = 2
2169 2170
2170 2171 put :update,
2171 2172 :id => issue.id,
2172 2173 :issue => {
2173 2174 :fixed_version_id => 4
2174 2175 }
2175 2176
2176 2177 assert_response :redirect
2177 2178 issue.reload
2178 2179 assert_equal 4, issue.fixed_version_id
2179 2180 assert_not_equal issue.project_id, issue.fixed_version.project_id
2180 2181 end
2181 2182
2182 2183 def test_put_update_should_redirect_back_using_the_back_url_parameter
2183 2184 issue = Issue.find(2)
2184 2185 @request.session[:user_id] = 2
2185 2186
2186 2187 put :update,
2187 2188 :id => issue.id,
2188 2189 :issue => {
2189 2190 :fixed_version_id => 4
2190 2191 },
2191 2192 :back_url => '/issues'
2192 2193
2193 2194 assert_response :redirect
2194 2195 assert_redirected_to '/issues'
2195 2196 end
2196 2197
2197 2198 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2198 2199 issue = Issue.find(2)
2199 2200 @request.session[:user_id] = 2
2200 2201
2201 2202 put :update,
2202 2203 :id => issue.id,
2203 2204 :issue => {
2204 2205 :fixed_version_id => 4
2205 2206 },
2206 2207 :back_url => 'http://google.com'
2207 2208
2208 2209 assert_response :redirect
2209 2210 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
2210 2211 end
2211 2212
2212 2213 def test_get_bulk_edit
2213 2214 @request.session[:user_id] = 2
2214 2215 get :bulk_edit, :ids => [1, 2]
2215 2216 assert_response :success
2216 2217 assert_template 'bulk_edit'
2217 2218
2218 2219 assert_tag :select, :attributes => {:name => 'issue[project_id]'}
2219 2220 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2220 2221
2221 2222 # Project specific custom field, date type
2222 2223 field = CustomField.find(9)
2223 2224 assert !field.is_for_all?
2224 2225 assert_equal 'date', field.field_format
2225 2226 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2226 2227
2227 2228 # System wide custom field
2228 2229 assert CustomField.find(1).is_for_all?
2229 2230 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
2230 2231
2231 2232 # Be sure we don't display inactive IssuePriorities
2232 2233 assert ! IssuePriority.find(15).active?
2233 2234 assert_no_tag :option, :attributes => {:value => '15'},
2234 2235 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2235 2236 end
2236 2237
2237 2238 def test_get_bulk_edit_on_different_projects
2238 2239 @request.session[:user_id] = 2
2239 2240 get :bulk_edit, :ids => [1, 2, 6]
2240 2241 assert_response :success
2241 2242 assert_template 'bulk_edit'
2242 2243
2243 2244 # Can not set issues from different projects as children of an issue
2244 2245 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2245 2246
2246 2247 # Project specific custom field, date type
2247 2248 field = CustomField.find(9)
2248 2249 assert !field.is_for_all?
2249 2250 assert !field.project_ids.include?(Issue.find(6).project_id)
2250 2251 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2251 2252 end
2252 2253
2253 2254 def test_get_bulk_edit_with_user_custom_field
2254 2255 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
2255 2256
2256 2257 @request.session[:user_id] = 2
2257 2258 get :bulk_edit, :ids => [1, 2]
2258 2259 assert_response :success
2259 2260 assert_template 'bulk_edit'
2260 2261
2261 2262 assert_tag :select,
2262 2263 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2263 2264 :children => {
2264 2265 :only => {:tag => 'option'},
2265 2266 :count => Project.find(1).users.count + 1
2266 2267 }
2267 2268 end
2268 2269
2269 2270 def test_get_bulk_edit_with_version_custom_field
2270 2271 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2271 2272
2272 2273 @request.session[:user_id] = 2
2273 2274 get :bulk_edit, :ids => [1, 2]
2274 2275 assert_response :success
2275 2276 assert_template 'bulk_edit'
2276 2277
2277 2278 assert_tag :select,
2278 2279 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2279 2280 :children => {
2280 2281 :only => {:tag => 'option'},
2281 2282 :count => Project.find(1).shared_versions.count + 1
2282 2283 }
2283 2284 end
2284 2285
2285 2286 def test_bulk_update
2286 2287 @request.session[:user_id] = 2
2287 2288 # update issues priority
2288 2289 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2289 2290 :issue => {:priority_id => 7,
2290 2291 :assigned_to_id => '',
2291 2292 :custom_field_values => {'2' => ''}}
2292 2293
2293 2294 assert_response 302
2294 2295 # check that the issues were updated
2295 2296 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2296 2297
2297 2298 issue = Issue.find(1)
2298 2299 journal = issue.journals.find(:first, :order => 'created_on DESC')
2299 2300 assert_equal '125', issue.custom_value_for(2).value
2300 2301 assert_equal 'Bulk editing', journal.notes
2301 2302 assert_equal 1, journal.details.size
2302 2303 end
2303 2304
2304 2305 def test_bulk_update_with_group_assignee
2305 2306 group = Group.find(11)
2306 2307 project = Project.find(1)
2307 2308 project.members << Member.new(:principal => group, :roles => [Role.first])
2308 2309
2309 2310 @request.session[:user_id] = 2
2310 2311 # update issues assignee
2311 2312 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2312 2313 :issue => {:priority_id => '',
2313 2314 :assigned_to_id => group.id,
2314 2315 :custom_field_values => {'2' => ''}}
2315 2316
2316 2317 assert_response 302
2317 2318 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2318 2319 end
2319 2320
2320 2321 def test_bulk_update_on_different_projects
2321 2322 @request.session[:user_id] = 2
2322 2323 # update issues priority
2323 2324 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2324 2325 :issue => {:priority_id => 7,
2325 2326 :assigned_to_id => '',
2326 2327 :custom_field_values => {'2' => ''}}
2327 2328
2328 2329 assert_response 302
2329 2330 # check that the issues were updated
2330 2331 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2331 2332
2332 2333 issue = Issue.find(1)
2333 2334 journal = issue.journals.find(:first, :order => 'created_on DESC')
2334 2335 assert_equal '125', issue.custom_value_for(2).value
2335 2336 assert_equal 'Bulk editing', journal.notes
2336 2337 assert_equal 1, journal.details.size
2337 2338 end
2338 2339
2339 2340 def test_bulk_update_on_different_projects_without_rights
2340 2341 @request.session[:user_id] = 3
2341 2342 user = User.find(3)
2342 2343 action = { :controller => "issues", :action => "bulk_update" }
2343 2344 assert user.allowed_to?(action, Issue.find(1).project)
2344 2345 assert ! user.allowed_to?(action, Issue.find(6).project)
2345 2346 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2346 2347 :issue => {:priority_id => 7,
2347 2348 :assigned_to_id => '',
2348 2349 :custom_field_values => {'2' => ''}}
2349 2350 assert_response 403
2350 2351 assert_not_equal "Bulk should fail", Journal.last.notes
2351 2352 end
2352 2353
2353 2354 def test_bullk_update_should_send_a_notification
2354 2355 @request.session[:user_id] = 2
2355 2356 ActionMailer::Base.deliveries.clear
2356 2357 post(:bulk_update,
2357 2358 {
2358 2359 :ids => [1, 2],
2359 2360 :notes => 'Bulk editing',
2360 2361 :issue => {
2361 2362 :priority_id => 7,
2362 2363 :assigned_to_id => '',
2363 2364 :custom_field_values => {'2' => ''}
2364 2365 }
2365 2366 })
2366 2367
2367 2368 assert_response 302
2368 2369 assert_equal 2, ActionMailer::Base.deliveries.size
2369 2370 end
2370 2371
2371 2372 def test_bulk_update_project
2372 2373 @request.session[:user_id] = 2
2373 2374 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
2374 2375 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2375 2376 # Issues moved to project 2
2376 2377 assert_equal 2, Issue.find(1).project_id
2377 2378 assert_equal 2, Issue.find(2).project_id
2378 2379 # No tracker change
2379 2380 assert_equal 1, Issue.find(1).tracker_id
2380 2381 assert_equal 2, Issue.find(2).tracker_id
2381 2382 end
2382 2383
2383 2384 def test_bulk_update_project_on_single_issue_should_follow_when_needed
2384 2385 @request.session[:user_id] = 2
2385 2386 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
2386 2387 assert_redirected_to '/issues/1'
2387 2388 end
2388 2389
2389 2390 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
2390 2391 @request.session[:user_id] = 2
2391 2392 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
2392 2393 assert_redirected_to '/projects/onlinestore/issues'
2393 2394 end
2394 2395
2395 2396 def test_bulk_update_tracker
2396 2397 @request.session[:user_id] = 2
2397 2398 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
2398 2399 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2399 2400 assert_equal 2, Issue.find(1).tracker_id
2400 2401 assert_equal 2, Issue.find(2).tracker_id
2401 2402 end
2402 2403
2403 2404 def test_bulk_update_status
2404 2405 @request.session[:user_id] = 2
2405 2406 # update issues priority
2406 2407 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2407 2408 :issue => {:priority_id => '',
2408 2409 :assigned_to_id => '',
2409 2410 :status_id => '5'}
2410 2411
2411 2412 assert_response 302
2412 2413 issue = Issue.find(1)
2413 2414 assert issue.closed?
2414 2415 end
2415 2416
2416 2417 def test_bulk_update_priority
2417 2418 @request.session[:user_id] = 2
2418 2419 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2419 2420
2420 2421 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2421 2422 assert_equal 6, Issue.find(1).priority_id
2422 2423 assert_equal 6, Issue.find(2).priority_id
2423 2424 end
2424 2425
2425 2426 def test_bulk_update_with_notes
2426 2427 @request.session[:user_id] = 2
2427 2428 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
2428 2429
2429 2430 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2430 2431 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
2431 2432 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
2432 2433 end
2433 2434
2434 2435 def test_bulk_update_parent_id
2435 2436 @request.session[:user_id] = 2
2436 2437 post :bulk_update, :ids => [1, 3],
2437 2438 :notes => 'Bulk editing parent',
2438 2439 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2439 2440
2440 2441 assert_response 302
2441 2442 parent = Issue.find(2)
2442 2443 assert_equal parent.id, Issue.find(1).parent_id
2443 2444 assert_equal parent.id, Issue.find(3).parent_id
2444 2445 assert_equal [1, 3], parent.children.collect(&:id).sort
2445 2446 end
2446 2447
2447 2448 def test_bulk_update_custom_field
2448 2449 @request.session[:user_id] = 2
2449 2450 # update issues priority
2450 2451 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2451 2452 :issue => {:priority_id => '',
2452 2453 :assigned_to_id => '',
2453 2454 :custom_field_values => {'2' => '777'}}
2454 2455
2455 2456 assert_response 302
2456 2457
2457 2458 issue = Issue.find(1)
2458 2459 journal = issue.journals.find(:first, :order => 'created_on DESC')
2459 2460 assert_equal '777', issue.custom_value_for(2).value
2460 2461 assert_equal 1, journal.details.size
2461 2462 assert_equal '125', journal.details.first.old_value
2462 2463 assert_equal '777', journal.details.first.value
2463 2464 end
2464 2465
2465 2466 def test_bulk_update_unassign
2466 2467 assert_not_nil Issue.find(2).assigned_to
2467 2468 @request.session[:user_id] = 2
2468 2469 # unassign issues
2469 2470 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
2470 2471 assert_response 302
2471 2472 # check that the issues were updated
2472 2473 assert_nil Issue.find(2).assigned_to
2473 2474 end
2474 2475
2475 2476 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
2476 2477 @request.session[:user_id] = 2
2477 2478
2478 2479 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
2479 2480
2480 2481 assert_response :redirect
2481 2482 issues = Issue.find([1,2])
2482 2483 issues.each do |issue|
2483 2484 assert_equal 4, issue.fixed_version_id
2484 2485 assert_not_equal issue.project_id, issue.fixed_version.project_id
2485 2486 end
2486 2487 end
2487 2488
2488 2489 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
2489 2490 @request.session[:user_id] = 2
2490 2491 post :bulk_update, :ids => [1,2], :back_url => '/issues'
2491 2492
2492 2493 assert_response :redirect
2493 2494 assert_redirected_to '/issues'
2494 2495 end
2495 2496
2496 2497 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2497 2498 @request.session[:user_id] = 2
2498 2499 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
2499 2500
2500 2501 assert_response :redirect
2501 2502 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
2502 2503 end
2503 2504
2504 2505 def test_bulk_copy_to_another_project
2505 2506 @request.session[:user_id] = 2
2506 2507 assert_difference 'Issue.count', 2 do
2507 2508 assert_no_difference 'Project.find(1).issues.count' do
2508 2509 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
2509 2510 end
2510 2511 end
2511 2512 assert_redirected_to '/projects/ecookbook/issues'
2512 2513 end
2513 2514
2514 2515 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
2515 2516 @request.session[:user_id] = 2
2516 2517 issue_before_move = Issue.find(1)
2517 2518 assert_difference 'Issue.count', 1 do
2518 2519 assert_no_difference 'Project.find(1).issues.count' do
2519 2520 post :bulk_update, :ids => [1], :copy => '1',
2520 2521 :issue => {
2521 2522 :project_id => '2', :tracker_id => '', :assigned_to_id => '',
2522 2523 :status_id => '', :start_date => '', :due_date => ''
2523 2524 }
2524 2525 end
2525 2526 end
2526 2527 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
2527 2528 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
2528 2529 assert_equal issue_before_move.status_id, issue_after_move.status_id
2529 2530 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
2530 2531 end
2531 2532
2532 2533 def test_bulk_copy_should_allow_changing_the_issue_attributes
2533 2534 # Fixes random test failure with Mysql
2534 2535 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
2535 2536 # doesn't return the expected results
2536 2537 Issue.delete_all("project_id=2")
2537 2538
2538 2539 @request.session[:user_id] = 2
2539 2540 assert_difference 'Issue.count', 2 do
2540 2541 assert_no_difference 'Project.find(1).issues.count' do
2541 2542 post :bulk_update, :ids => [1, 2], :copy => '1',
2542 2543 :issue => {
2543 2544 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
2544 2545 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
2545 2546 }
2546 2547 end
2547 2548 end
2548 2549
2549 2550 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
2550 2551 assert_equal 2, copied_issues.size
2551 2552 copied_issues.each do |issue|
2552 2553 assert_equal 2, issue.project_id, "Project is incorrect"
2553 2554 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
2554 2555 assert_equal 3, issue.status_id, "Status is incorrect"
2555 2556 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
2556 2557 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
2557 2558 end
2558 2559 end
2559 2560
2560 2561 def test_bulk_copy_should_allow_adding_a_note
2561 2562 @request.session[:user_id] = 2
2562 2563 assert_difference 'Issue.count', 1 do
2563 2564 post :bulk_update, :ids => [1], :copy => '1',
2564 2565 :notes => 'Copying one issue',
2565 2566 :issue => {
2566 2567 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
2567 2568 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
2568 2569 }
2569 2570 end
2570 2571
2571 2572 issue = Issue.first(:order => 'id DESC')
2572 2573 assert_equal 1, issue.journals.size
2573 2574 journal = issue.journals.first
2574 2575 assert_equal 0, journal.details.size
2575 2576 assert_equal 'Copying one issue', journal.notes
2576 2577 end
2577 2578
2578 2579 def test_bulk_copy_to_another_project_should_follow_when_needed
2579 2580 @request.session[:user_id] = 2
2580 2581 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
2581 2582 issue = Issue.first(:order => 'id DESC')
2582 2583 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
2583 2584 end
2584 2585
2585 2586 def test_destroy_issue_with_no_time_entries
2586 2587 assert_nil TimeEntry.find_by_issue_id(2)
2587 2588 @request.session[:user_id] = 2
2588 2589
2589 2590 assert_difference 'Issue.count', -1 do
2590 2591 delete :destroy, :id => 2
2591 2592 end
2592 2593 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2593 2594 assert_nil Issue.find_by_id(2)
2594 2595 end
2595 2596
2596 2597 def test_destroy_issues_with_time_entries
2597 2598 @request.session[:user_id] = 2
2598 2599
2599 2600 assert_no_difference 'Issue.count' do
2600 2601 delete :destroy, :ids => [1, 3]
2601 2602 end
2602 2603 assert_response :success
2603 2604 assert_template 'destroy'
2604 2605 assert_not_nil assigns(:hours)
2605 2606 assert Issue.find_by_id(1) && Issue.find_by_id(3)
2606 2607 assert_tag 'form',
2607 2608 :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}}
2608 2609 end
2609 2610
2610 2611 def test_destroy_issues_and_destroy_time_entries
2611 2612 @request.session[:user_id] = 2
2612 2613
2613 2614 assert_difference 'Issue.count', -2 do
2614 2615 assert_difference 'TimeEntry.count', -3 do
2615 2616 delete :destroy, :ids => [1, 3], :todo => 'destroy'
2616 2617 end
2617 2618 end
2618 2619 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2619 2620 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2620 2621 assert_nil TimeEntry.find_by_id([1, 2])
2621 2622 end
2622 2623
2623 2624 def test_destroy_issues_and_assign_time_entries_to_project
2624 2625 @request.session[:user_id] = 2
2625 2626
2626 2627 assert_difference 'Issue.count', -2 do
2627 2628 assert_no_difference 'TimeEntry.count' do
2628 2629 delete :destroy, :ids => [1, 3], :todo => 'nullify'
2629 2630 end
2630 2631 end
2631 2632 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2632 2633 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2633 2634 assert_nil TimeEntry.find(1).issue_id
2634 2635 assert_nil TimeEntry.find(2).issue_id
2635 2636 end
2636 2637
2637 2638 def test_destroy_issues_and_reassign_time_entries_to_another_issue
2638 2639 @request.session[:user_id] = 2
2639 2640
2640 2641 assert_difference 'Issue.count', -2 do
2641 2642 assert_no_difference 'TimeEntry.count' do
2642 2643 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
2643 2644 end
2644 2645 end
2645 2646 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2646 2647 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2647 2648 assert_equal 2, TimeEntry.find(1).issue_id
2648 2649 assert_equal 2, TimeEntry.find(2).issue_id
2649 2650 end
2650 2651
2651 2652 def test_destroy_issues_from_different_projects
2652 2653 @request.session[:user_id] = 2
2653 2654
2654 2655 assert_difference 'Issue.count', -3 do
2655 2656 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
2656 2657 end
2657 2658 assert_redirected_to :controller => 'issues', :action => 'index'
2658 2659 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
2659 2660 end
2660 2661
2661 2662 def test_destroy_parent_and_child_issues
2662 2663 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
2663 2664 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
2664 2665 assert child.is_descendant_of?(parent.reload)
2665 2666
2666 2667 @request.session[:user_id] = 2
2667 2668 assert_difference 'Issue.count', -2 do
2668 2669 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
2669 2670 end
2670 2671 assert_response 302
2671 2672 end
2672 2673
2673 2674 def test_default_search_scope
2674 2675 get :index
2675 2676 assert_tag :div, :attributes => {:id => 'quick-search'},
2676 2677 :child => {:tag => 'form',
2677 2678 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
2678 2679 end
2679 2680 end
@@ -1,88 +1,167
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class CustomFieldTest < ActiveSupport::TestCase
21 21 fixtures :custom_fields
22 22
23 23 def test_create
24 24 field = UserCustomField.new(:name => 'Money money money', :field_format => 'float')
25 25 assert field.save
26 26 end
27 27
28 28 def test_before_validation
29 29 field = CustomField.new(:name => 'test_before_validation', :field_format => 'int')
30 30 field.searchable = true
31 31 assert field.save
32 32 assert_equal false, field.searchable
33 33 field.searchable = true
34 34 assert field.save
35 35 assert_equal false, field.searchable
36 36 end
37 37
38 38 def test_regexp_validation
39 39 field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9')
40 40 assert !field.save
41 41 assert_equal I18n.t('activerecord.errors.messages.invalid'),
42 42 field.errors[:regexp].to_s
43 43 field.regexp = '[a-z0-9]'
44 44 assert field.save
45 45 end
46 46
47 def test_default_value_should_be_validated
48 field = CustomField.new(:name => 'Test', :field_format => 'int')
49 field.default_value = 'abc'
50 assert !field.valid?
51 field.default_value = '6'
52 assert field.valid?
53 end
54
47 55 def test_possible_values_should_accept_an_array
48 56 field = CustomField.new
49 57 field.possible_values = ["One value", ""]
50 58 assert_equal ["One value"], field.possible_values
51 59 end
52 60
53 61 def test_possible_values_should_accept_a_string
54 62 field = CustomField.new
55 63 field.possible_values = "One value"
56 64 assert_equal ["One value"], field.possible_values
57 65 end
58 66
59 67 def test_possible_values_should_accept_a_multiline_string
60 68 field = CustomField.new
61 69 field.possible_values = "One value\nAnd another one \r\n \n"
62 70 assert_equal ["One value", "And another one"], field.possible_values
63 71 end
64 72
65 73 def test_destroy
66 74 field = CustomField.find(1)
67 75 assert field.destroy
68 76 end
69 77
70 78 def test_new_subclass_instance_should_return_an_instance
71 79 f = CustomField.new_subclass_instance('IssueCustomField')
72 80 assert_kind_of IssueCustomField, f
73 81 end
74 82
75 83 def test_new_subclass_instance_should_set_attributes
76 84 f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test')
77 85 assert_kind_of IssueCustomField, f
78 86 assert_equal 'Test', f.name
79 87 end
80 88
81 89 def test_new_subclass_instance_with_invalid_class_name_should_return_nil
82 90 assert_nil CustomField.new_subclass_instance('WrongClassName')
83 91 end
84 92
85 93 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
86 94 assert_nil CustomField.new_subclass_instance('Project')
87 95 end
96
97 def test_string_field_validation_with_blank_value
98 f = CustomField.new(:field_format => 'string')
99
100 assert f.valid_field_value?(nil)
101 assert f.valid_field_value?('')
102
103 f.is_required = true
104 assert !f.valid_field_value?(nil)
105 assert !f.valid_field_value?('')
106 end
107
108 def test_string_field_validation_with_min_and_max_lengths
109 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
110
111 assert f.valid_field_value?(nil)
112 assert f.valid_field_value?('')
113 assert f.valid_field_value?('a' * 2)
114 assert !f.valid_field_value?('a')
115 assert !f.valid_field_value?('a' * 6)
116 end
117
118 def test_string_field_validation_with_regexp
119 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
120
121 assert f.valid_field_value?(nil)
122 assert f.valid_field_value?('')
123 assert f.valid_field_value?('ABC')
124 assert !f.valid_field_value?('abc')
125 end
126
127 def test_date_field_validation
128 f = CustomField.new(:field_format => 'date')
129
130 assert f.valid_field_value?(nil)
131 assert f.valid_field_value?('')
132 assert f.valid_field_value?('1975-07-14')
133 assert !f.valid_field_value?('1975-07-33')
134 assert !f.valid_field_value?('abc')
135 end
136
137 def test_list_field_validation
138 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
139
140 assert f.valid_field_value?(nil)
141 assert f.valid_field_value?('')
142 assert f.valid_field_value?('value2')
143 assert !f.valid_field_value?('abc')
144 end
145
146 def test_int_field_validation
147 f = CustomField.new(:field_format => 'int')
148
149 assert f.valid_field_value?(nil)
150 assert f.valid_field_value?('')
151 assert f.valid_field_value?('123')
152 assert f.valid_field_value?('+123')
153 assert f.valid_field_value?('-123')
154 assert !f.valid_field_value?('6abc')
155 end
156
157 def test_float_field_validation
158 f = CustomField.new(:field_format => 'float')
159
160 assert f.valid_field_value?(nil)
161 assert f.valid_field_value?('')
162 assert f.valid_field_value?('11.2')
163 assert f.valid_field_value?('-6.250')
164 assert f.valid_field_value?('5')
165 assert !f.valid_field_value?('6abc')
166 end
88 167 end
@@ -1,125 +1,39
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class CustomValueTest < ActiveSupport::TestCase
21 21 fixtures :custom_fields, :custom_values, :users
22 22
23 def test_string_field_validation_with_blank_value
24 f = CustomField.new(:field_format => 'string')
25 v = CustomValue.new(:custom_field => f)
26
27 v.value = nil
28 assert v.valid?
29 v.value = ''
30 assert v.valid?
31
32 f.is_required = true
33 v.value = nil
34 assert !v.valid?
35 v.value = ''
36 assert !v.valid?
37 end
38
39 def test_string_field_validation_with_min_and_max_lengths
40 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
41 v = CustomValue.new(:custom_field => f, :value => '')
42 assert v.valid?
43 v.value = 'a'
44 assert !v.valid?
45 v.value = 'a' * 2
46 assert v.valid?
47 v.value = 'a' * 6
48 assert !v.valid?
49 end
50
51 def test_string_field_validation_with_regexp
52 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
53 v = CustomValue.new(:custom_field => f, :value => '')
54 assert v.valid?
55 v.value = 'abc'
56 assert !v.valid?
57 v.value = 'ABC'
58 assert v.valid?
59 end
60
61 def test_date_field_validation
62 f = CustomField.new(:field_format => 'date')
63 v = CustomValue.new(:custom_field => f, :value => '')
64 assert v.valid?
65 v.value = 'abc'
66 assert !v.valid?
67 v.value = '1975-07-33'
68 assert !v.valid?
69 v.value = '1975-07-14'
70 assert v.valid?
71 end
72
73 def test_list_field_validation
74 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
75 v = CustomValue.new(:custom_field => f, :value => '')
76 assert v.valid?
77 v.value = 'abc'
78 assert !v.valid?
79 v.value = 'value2'
80 assert v.valid?
81 end
82
83 def test_int_field_validation
84 f = CustomField.new(:field_format => 'int')
85 v = CustomValue.new(:custom_field => f, :value => '')
86 assert v.valid?
87 v.value = 'abc'
88 assert !v.valid?
89 v.value = '123'
90 assert v.valid?
91 v.value = '+123'
92 assert v.valid?
93 v.value = '-123'
94 assert v.valid?
95 end
96
97 def test_float_field_validation
98 v = CustomValue.new(:customized => User.find(:first), :custom_field => UserCustomField.find_by_name('Money'))
99 v.value = '11.2'
100 assert v.save
101 v.value = ''
102 assert v.save
103 v.value = '-6.250'
104 assert v.save
105 v.value = '6a'
106 assert !v.save
107 end
108
109 23 def test_default_value
110 24 field = CustomField.find_by_default_value('Default string')
111 25 assert_not_nil field
112 26
113 27 v = CustomValue.new(:custom_field => field)
114 28 assert_equal 'Default string', v.value
115 29
116 30 v = CustomValue.new(:custom_field => field, :value => 'Not empty')
117 31 assert_equal 'Not empty', v.value
118 32 end
119 33
120 34 def test_sti_polymorphic_association
121 35 # Rails uses top level sti class for polymorphic association. See #3978.
122 36 assert !User.find(4).custom_values.empty?
123 37 assert !CustomValue.find(2).customized.nil?
124 38 end
125 39 end
@@ -1,1213 +1,1212
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssueTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles,
22 22 :groups_users,
23 23 :trackers, :projects_trackers,
24 24 :enabled_modules,
25 25 :versions,
26 26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 27 :enumerations,
28 28 :issues,
29 29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 30 :time_entries
31 31
32 include Redmine::I18n
33
32 34 def test_create
33 35 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
34 36 :status_id => 1, :priority => IssuePriority.all.first,
35 37 :subject => 'test_create',
36 38 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
37 39 assert issue.save
38 40 issue.reload
39 41 assert_equal 1.5, issue.estimated_hours
40 42 end
41 43
42 44 def test_create_minimal
43 45 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
44 46 :status_id => 1, :priority => IssuePriority.all.first,
45 47 :subject => 'test_create')
46 48 assert issue.save
47 49 assert issue.description.nil?
48 50 end
49 51
50 52 def test_create_with_required_custom_field
53 set_language_if_valid 'en'
51 54 field = IssueCustomField.find_by_name('Database')
52 55 field.update_attribute(:is_required, true)
53 56
54 57 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
55 58 :status_id => 1, :subject => 'test_create',
56 59 :description => 'IssueTest#test_create_with_required_custom_field')
57 60 assert issue.available_custom_fields.include?(field)
58 61 # No value for the custom field
59 62 assert !issue.save
60 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
61 issue.errors[:custom_values].to_s
63 assert_equal "Database can't be blank", issue.errors[:base].to_s
62 64 # Blank value
63 65 issue.custom_field_values = { field.id => '' }
64 66 assert !issue.save
65 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
66 issue.errors[:custom_values].to_s
67 assert_equal "Database can't be blank", issue.errors[:base].to_s
67 68 # Invalid value
68 69 issue.custom_field_values = { field.id => 'SQLServer' }
69 70 assert !issue.save
70 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
71 issue.errors[:custom_values].to_s
71 assert_equal "Database is not included in the list", issue.errors[:base].to_s
72 72 # Valid value
73 73 issue.custom_field_values = { field.id => 'PostgreSQL' }
74 74 assert issue.save
75 75 issue.reload
76 76 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
77 77 end
78 78
79 79 def test_create_with_group_assignment
80 80 with_settings :issue_group_assignment => '1' do
81 81 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
82 82 :subject => 'Group assignment',
83 83 :assigned_to_id => 11).save
84 84 issue = Issue.first(:order => 'id DESC')
85 85 assert_kind_of Group, issue.assigned_to
86 86 assert_equal Group.find(11), issue.assigned_to
87 87 end
88 88 end
89 89
90 90 def assert_visibility_match(user, issues)
91 91 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
92 92 end
93 93
94 94 def test_visible_scope_for_anonymous
95 95 # Anonymous user should see issues of public projects only
96 96 issues = Issue.visible(User.anonymous).all
97 97 assert issues.any?
98 98 assert_nil issues.detect {|issue| !issue.project.is_public?}
99 99 assert_nil issues.detect {|issue| issue.is_private?}
100 100 assert_visibility_match User.anonymous, issues
101 101 end
102 102
103 103 def test_visible_scope_for_anonymous_with_own_issues_visibility
104 104 Role.anonymous.update_attribute :issues_visibility, 'own'
105 105 Issue.create!(:project_id => 1, :tracker_id => 1,
106 106 :author_id => User.anonymous.id,
107 107 :subject => 'Issue by anonymous')
108 108
109 109 issues = Issue.visible(User.anonymous).all
110 110 assert issues.any?
111 111 assert_nil issues.detect {|issue| issue.author != User.anonymous}
112 112 assert_visibility_match User.anonymous, issues
113 113 end
114 114
115 115 def test_visible_scope_for_anonymous_without_view_issues_permissions
116 116 # Anonymous user should not see issues without permission
117 117 Role.anonymous.remove_permission!(:view_issues)
118 118 issues = Issue.visible(User.anonymous).all
119 119 assert issues.empty?
120 120 assert_visibility_match User.anonymous, issues
121 121 end
122 122
123 123 def test_visible_scope_for_non_member
124 124 user = User.find(9)
125 125 assert user.projects.empty?
126 126 # Non member user should see issues of public projects only
127 127 issues = Issue.visible(user).all
128 128 assert issues.any?
129 129 assert_nil issues.detect {|issue| !issue.project.is_public?}
130 130 assert_nil issues.detect {|issue| issue.is_private?}
131 131 assert_visibility_match user, issues
132 132 end
133 133
134 134 def test_visible_scope_for_non_member_with_own_issues_visibility
135 135 Role.non_member.update_attribute :issues_visibility, 'own'
136 136 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
137 137 user = User.find(9)
138 138
139 139 issues = Issue.visible(user).all
140 140 assert issues.any?
141 141 assert_nil issues.detect {|issue| issue.author != user}
142 142 assert_visibility_match user, issues
143 143 end
144 144
145 145 def test_visible_scope_for_non_member_without_view_issues_permissions
146 146 # Non member user should not see issues without permission
147 147 Role.non_member.remove_permission!(:view_issues)
148 148 user = User.find(9)
149 149 assert user.projects.empty?
150 150 issues = Issue.visible(user).all
151 151 assert issues.empty?
152 152 assert_visibility_match user, issues
153 153 end
154 154
155 155 def test_visible_scope_for_member
156 156 user = User.find(9)
157 157 # User should see issues of projects for which he has view_issues permissions only
158 158 Role.non_member.remove_permission!(:view_issues)
159 159 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
160 160 issues = Issue.visible(user).all
161 161 assert issues.any?
162 162 assert_nil issues.detect {|issue| issue.project_id != 3}
163 163 assert_nil issues.detect {|issue| issue.is_private?}
164 164 assert_visibility_match user, issues
165 165 end
166 166
167 167 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
168 168 user = User.find(8)
169 169 assert user.groups.any?
170 170 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
171 171 Role.non_member.remove_permission!(:view_issues)
172 172
173 173 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
174 174 :status_id => 1, :priority => IssuePriority.all.first,
175 175 :subject => 'Assignment test',
176 176 :assigned_to => user.groups.first,
177 177 :is_private => true)
178 178
179 179 Role.find(2).update_attribute :issues_visibility, 'default'
180 180 issues = Issue.visible(User.find(8)).all
181 181 assert issues.any?
182 182 assert issues.include?(issue)
183 183
184 184 Role.find(2).update_attribute :issues_visibility, 'own'
185 185 issues = Issue.visible(User.find(8)).all
186 186 assert issues.any?
187 187 assert issues.include?(issue)
188 188 end
189 189
190 190 def test_visible_scope_for_admin
191 191 user = User.find(1)
192 192 user.members.each(&:destroy)
193 193 assert user.projects.empty?
194 194 issues = Issue.visible(user).all
195 195 assert issues.any?
196 196 # Admin should see issues on private projects that he does not belong to
197 197 assert issues.detect {|issue| !issue.project.is_public?}
198 198 # Admin should see private issues of other users
199 199 assert issues.detect {|issue| issue.is_private? && issue.author != user}
200 200 assert_visibility_match user, issues
201 201 end
202 202
203 203 def test_visible_scope_with_project
204 204 project = Project.find(1)
205 205 issues = Issue.visible(User.find(2), :project => project).all
206 206 projects = issues.collect(&:project).uniq
207 207 assert_equal 1, projects.size
208 208 assert_equal project, projects.first
209 209 end
210 210
211 211 def test_visible_scope_with_project_and_subprojects
212 212 project = Project.find(1)
213 213 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
214 214 projects = issues.collect(&:project).uniq
215 215 assert projects.size > 1
216 216 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
217 217 end
218 218
219 219 def test_visible_and_nested_set_scopes
220 220 assert_equal 0, Issue.find(1).descendants.visible.all.size
221 221 end
222 222
223 223 def test_open_scope
224 224 issues = Issue.open.all
225 225 assert_nil issues.detect(&:closed?)
226 226 end
227 227
228 228 def test_open_scope_with_arg
229 229 issues = Issue.open(false).all
230 230 assert_equal issues, issues.select(&:closed?)
231 231 end
232 232
233 233 def test_errors_full_messages_should_include_custom_fields_errors
234 234 field = IssueCustomField.find_by_name('Database')
235 235
236 236 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
237 237 :status_id => 1, :subject => 'test_create',
238 238 :description => 'IssueTest#test_create_with_required_custom_field')
239 239 assert issue.available_custom_fields.include?(field)
240 240 # Invalid value
241 241 issue.custom_field_values = { field.id => 'SQLServer' }
242 242
243 243 assert !issue.valid?
244 244 assert_equal 1, issue.errors.full_messages.size
245 245 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
246 246 issue.errors.full_messages.first
247 247 end
248 248
249 249 def test_update_issue_with_required_custom_field
250 250 field = IssueCustomField.find_by_name('Database')
251 251 field.update_attribute(:is_required, true)
252 252
253 253 issue = Issue.find(1)
254 254 assert_nil issue.custom_value_for(field)
255 255 assert issue.available_custom_fields.include?(field)
256 256 # No change to custom values, issue can be saved
257 257 assert issue.save
258 258 # Blank value
259 259 issue.custom_field_values = { field.id => '' }
260 260 assert !issue.save
261 261 # Valid value
262 262 issue.custom_field_values = { field.id => 'PostgreSQL' }
263 263 assert issue.save
264 264 issue.reload
265 265 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
266 266 end
267 267
268 268 def test_should_not_update_attributes_if_custom_fields_validation_fails
269 269 issue = Issue.find(1)
270 270 field = IssueCustomField.find_by_name('Database')
271 271 assert issue.available_custom_fields.include?(field)
272 272
273 273 issue.custom_field_values = { field.id => 'Invalid' }
274 274 issue.subject = 'Should be not be saved'
275 275 assert !issue.save
276 276
277 277 issue.reload
278 278 assert_equal "Can't print recipes", issue.subject
279 279 end
280 280
281 281 def test_should_not_recreate_custom_values_objects_on_update
282 282 field = IssueCustomField.find_by_name('Database')
283 283
284 284 issue = Issue.find(1)
285 285 issue.custom_field_values = { field.id => 'PostgreSQL' }
286 286 assert issue.save
287 287 custom_value = issue.custom_value_for(field)
288 288 issue.reload
289 289 issue.custom_field_values = { field.id => 'MySQL' }
290 290 assert issue.save
291 291 issue.reload
292 292 assert_equal custom_value.id, issue.custom_value_for(field).id
293 293 end
294 294
295 295 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
296 296 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'})
297 297 assert !Tracker.find(2).custom_field_ids.include?(2)
298 298
299 299 issue = Issue.find(issue.id)
300 300 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
301 301
302 302 issue = Issue.find(issue.id)
303 303 custom_value = issue.custom_value_for(2)
304 304 assert_not_nil custom_value
305 305 assert_equal 'Test', custom_value.value
306 306 end
307 307
308 308 def test_assigning_tracker_id_should_reload_custom_fields_values
309 309 issue = Issue.new(:project => Project.find(1))
310 310 assert issue.custom_field_values.empty?
311 311 issue.tracker_id = 1
312 312 assert issue.custom_field_values.any?
313 313 end
314 314
315 315 def test_assigning_attributes_should_assign_project_and_tracker_first
316 316 seq = sequence('seq')
317 317 issue = Issue.new
318 318 issue.expects(:project_id=).in_sequence(seq)
319 319 issue.expects(:tracker_id=).in_sequence(seq)
320 320 issue.expects(:subject=).in_sequence(seq)
321 321 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
322 322 end
323 323
324 324 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
325 325 attributes = ActiveSupport::OrderedHash.new
326 326 attributes['custom_field_values'] = { '1' => 'MySQL' }
327 327 attributes['tracker_id'] = '1'
328 328 issue = Issue.new(:project => Project.find(1))
329 329 issue.attributes = attributes
330 assert_not_nil issue.custom_value_for(1)
331 assert_equal 'MySQL', issue.custom_value_for(1).value
330 assert_equal 'MySQL', issue.custom_field_value(1)
332 331 end
333 332
334 333 def test_should_update_issue_with_disabled_tracker
335 334 p = Project.find(1)
336 335 issue = Issue.find(1)
337 336
338 337 p.trackers.delete(issue.tracker)
339 338 assert !p.trackers.include?(issue.tracker)
340 339
341 340 issue.reload
342 341 issue.subject = 'New subject'
343 342 assert issue.save
344 343 end
345 344
346 345 def test_should_not_set_a_disabled_tracker
347 346 p = Project.find(1)
348 347 p.trackers.delete(Tracker.find(2))
349 348
350 349 issue = Issue.find(1)
351 350 issue.tracker_id = 2
352 351 issue.subject = 'New subject'
353 352 assert !issue.save
354 353 assert_not_nil issue.errors[:tracker_id]
355 354 end
356 355
357 356 def test_category_based_assignment
358 357 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
359 358 :status_id => 1, :priority => IssuePriority.all.first,
360 359 :subject => 'Assignment test',
361 360 :description => 'Assignment test', :category_id => 1)
362 361 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
363 362 end
364 363
365 364 def test_new_statuses_allowed_to
366 365 Workflow.delete_all
367 366
368 367 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
369 368 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
370 369 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
371 370 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
372 371 status = IssueStatus.find(1)
373 372 role = Role.find(1)
374 373 tracker = Tracker.find(1)
375 374 user = User.find(2)
376 375
377 376 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
378 377 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
379 378
380 379 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
381 380 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
382 381
383 382 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
384 383 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
385 384
386 385 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
387 386 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
388 387 end
389 388
390 389 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
391 390 admin = User.find(1)
392 391 issue = Issue.find(1)
393 392 assert !admin.member_of?(issue.project)
394 393 expected_statuses = [issue.status] + Workflow.find_all_by_old_status_id(issue.status_id).map(&:new_status).uniq.sort
395 394
396 395 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
397 396 end
398 397
399 398 def test_copy
400 399 issue = Issue.new.copy_from(1)
401 400 assert issue.copy?
402 401 assert issue.save
403 402 issue.reload
404 403 orig = Issue.find(1)
405 404 assert_equal orig.subject, issue.subject
406 405 assert_equal orig.tracker, issue.tracker
407 406 assert_equal "125", issue.custom_value_for(2).value
408 407 end
409 408
410 409 def test_copy_should_copy_status
411 410 orig = Issue.find(8)
412 411 assert orig.status != IssueStatus.default
413 412
414 413 issue = Issue.new.copy_from(orig)
415 414 assert issue.save
416 415 issue.reload
417 416 assert_equal orig.status, issue.status
418 417 end
419 418
420 419 def test_should_not_call_after_project_change_on_creation
421 420 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1)
422 421 issue.expects(:after_project_change).never
423 422 issue.save!
424 423 end
425 424
426 425 def test_should_not_call_after_project_change_on_update
427 426 issue = Issue.find(1)
428 427 issue.project = Project.find(1)
429 428 issue.subject = 'No project change'
430 429 issue.expects(:after_project_change).never
431 430 issue.save!
432 431 end
433 432
434 433 def test_should_call_after_project_change_on_project_change
435 434 issue = Issue.find(1)
436 435 issue.project = Project.find(2)
437 436 issue.expects(:after_project_change).once
438 437 issue.save!
439 438 end
440 439
441 440 def test_should_close_duplicates
442 441 # Create 3 issues
443 442 project = Project.find(1)
444 443 issue1 = Issue.generate_for_project!(project)
445 444 issue2 = Issue.generate_for_project!(project)
446 445 issue3 = Issue.generate_for_project!(project)
447 446
448 447 # 2 is a dupe of 1
449 448 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
450 449 # And 3 is a dupe of 2
451 450 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
452 451 # And 3 is a dupe of 1 (circular duplicates)
453 452 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
454 453
455 454 assert issue1.reload.duplicates.include?(issue2)
456 455
457 456 # Closing issue 1
458 457 issue1.init_journal(User.find(:first), "Closing issue1")
459 458 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
460 459 assert issue1.save
461 460 # 2 and 3 should be also closed
462 461 assert issue2.reload.closed?
463 462 assert issue3.reload.closed?
464 463 end
465 464
466 465 def test_should_not_close_duplicated_issue
467 466 project = Project.find(1)
468 467 issue1 = Issue.generate_for_project!(project)
469 468 issue2 = Issue.generate_for_project!(project)
470 469
471 470 # 2 is a dupe of 1
472 471 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
473 472 # 2 is a dup of 1 but 1 is not a duplicate of 2
474 473 assert !issue2.reload.duplicates.include?(issue1)
475 474
476 475 # Closing issue 2
477 476 issue2.init_journal(User.find(:first), "Closing issue2")
478 477 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
479 478 assert issue2.save
480 479 # 1 should not be also closed
481 480 assert !issue1.reload.closed?
482 481 end
483 482
484 483 def test_assignable_versions
485 484 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
486 485 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
487 486 end
488 487
489 488 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
490 489 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
491 490 assert !issue.save
492 491 assert_not_nil issue.errors[:fixed_version_id]
493 492 end
494 493
495 494 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
496 495 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
497 496 assert !issue.save
498 497 assert_not_nil issue.errors[:fixed_version_id]
499 498 end
500 499
501 500 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
502 501 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
503 502 assert issue.save
504 503 end
505 504
506 505 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
507 506 issue = Issue.find(11)
508 507 assert_equal 'closed', issue.fixed_version.status
509 508 issue.subject = 'Subject changed'
510 509 assert issue.save
511 510 end
512 511
513 512 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
514 513 issue = Issue.find(11)
515 514 issue.status_id = 1
516 515 assert !issue.save
517 516 assert_not_nil issue.errors[:base]
518 517 end
519 518
520 519 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
521 520 issue = Issue.find(11)
522 521 issue.status_id = 1
523 522 issue.fixed_version_id = 3
524 523 assert issue.save
525 524 end
526 525
527 526 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
528 527 issue = Issue.find(12)
529 528 assert_equal 'locked', issue.fixed_version.status
530 529 issue.status_id = 1
531 530 assert issue.save
532 531 end
533 532
534 533 def test_move_to_another_project_with_same_category
535 534 issue = Issue.find(1)
536 535 issue.project = Project.find(2)
537 536 assert issue.save
538 537 issue.reload
539 538 assert_equal 2, issue.project_id
540 539 # Category changes
541 540 assert_equal 4, issue.category_id
542 541 # Make sure time entries were move to the target project
543 542 assert_equal 2, issue.time_entries.first.project_id
544 543 end
545 544
546 545 def test_move_to_another_project_without_same_category
547 546 issue = Issue.find(2)
548 547 issue.project = Project.find(2)
549 548 assert issue.save
550 549 issue.reload
551 550 assert_equal 2, issue.project_id
552 551 # Category cleared
553 552 assert_nil issue.category_id
554 553 end
555 554
556 555 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
557 556 issue = Issue.find(1)
558 557 issue.update_attribute(:fixed_version_id, 1)
559 558 issue.project = Project.find(2)
560 559 assert issue.save
561 560 issue.reload
562 561 assert_equal 2, issue.project_id
563 562 # Cleared fixed_version
564 563 assert_equal nil, issue.fixed_version
565 564 end
566 565
567 566 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
568 567 issue = Issue.find(1)
569 568 issue.update_attribute(:fixed_version_id, 4)
570 569 issue.project = Project.find(5)
571 570 assert issue.save
572 571 issue.reload
573 572 assert_equal 5, issue.project_id
574 573 # Keep fixed_version
575 574 assert_equal 4, issue.fixed_version_id
576 575 end
577 576
578 577 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
579 578 issue = Issue.find(1)
580 579 issue.update_attribute(:fixed_version_id, 1)
581 580 issue.project = Project.find(5)
582 581 assert issue.save
583 582 issue.reload
584 583 assert_equal 5, issue.project_id
585 584 # Cleared fixed_version
586 585 assert_equal nil, issue.fixed_version
587 586 end
588 587
589 588 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
590 589 issue = Issue.find(1)
591 590 issue.update_attribute(:fixed_version_id, 7)
592 591 issue.project = Project.find(2)
593 592 assert issue.save
594 593 issue.reload
595 594 assert_equal 2, issue.project_id
596 595 # Keep fixed_version
597 596 assert_equal 7, issue.fixed_version_id
598 597 end
599 598
600 599 def test_move_to_another_project_with_disabled_tracker
601 600 issue = Issue.find(1)
602 601 target = Project.find(2)
603 602 target.tracker_ids = [3]
604 603 target.save
605 604 issue.project = target
606 605 assert issue.save
607 606 issue.reload
608 607 assert_equal 2, issue.project_id
609 608 assert_equal 3, issue.tracker_id
610 609 end
611 610
612 611 def test_copy_to_the_same_project
613 612 issue = Issue.find(1)
614 613 copy = issue.copy
615 614 assert_difference 'Issue.count' do
616 615 copy.save!
617 616 end
618 617 assert_kind_of Issue, copy
619 618 assert_equal issue.project, copy.project
620 619 assert_equal "125", copy.custom_value_for(2).value
621 620 end
622 621
623 622 def test_copy_to_another_project_and_tracker
624 623 issue = Issue.find(1)
625 624 copy = issue.copy(:project_id => 3, :tracker_id => 2)
626 625 assert_difference 'Issue.count' do
627 626 copy.save!
628 627 end
629 628 copy.reload
630 629 assert_kind_of Issue, copy
631 630 assert_equal Project.find(3), copy.project
632 631 assert_equal Tracker.find(2), copy.tracker
633 632 # Custom field #2 is not associated with target tracker
634 633 assert_nil copy.custom_value_for(2)
635 634 end
636 635
637 636 context "#copy" do
638 637 setup do
639 638 @issue = Issue.find(1)
640 639 end
641 640
642 641 should "not create a journal" do
643 642 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
644 643 copy.save!
645 644 assert_equal 0, copy.reload.journals.size
646 645 end
647 646
648 647 should "allow assigned_to changes" do
649 648 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
650 649 assert_equal 3, copy.assigned_to_id
651 650 end
652 651
653 652 should "allow status changes" do
654 653 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
655 654 assert_equal 2, copy.status_id
656 655 end
657 656
658 657 should "allow start date changes" do
659 658 date = Date.today
660 659 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
661 660 assert_equal date, copy.start_date
662 661 end
663 662
664 663 should "allow due date changes" do
665 664 date = Date.today
666 665 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date)
667 666 assert_equal date, copy.due_date
668 667 end
669 668
670 669 should "set current user as author" do
671 670 User.current = User.find(9)
672 671 copy = @issue.copy(:project_id => 3, :tracker_id => 2)
673 672 assert_equal User.current, copy.author
674 673 end
675 674
676 675 should "create a journal with notes" do
677 676 date = Date.today
678 677 notes = "Notes added when copying"
679 678 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
680 679 copy.init_journal(User.current, notes)
681 680 copy.save!
682 681
683 682 assert_equal 1, copy.journals.size
684 683 journal = copy.journals.first
685 684 assert_equal 0, journal.details.size
686 685 assert_equal notes, journal.notes
687 686 end
688 687 end
689 688
690 689 def test_recipients_should_include_previous_assignee
691 690 user = User.find(3)
692 691 user.members.update_all ["mail_notification = ?", false]
693 692 user.update_attribute :mail_notification, 'only_assigned'
694 693
695 694 issue = Issue.find(2)
696 695 issue.assigned_to = nil
697 696 assert_include user.mail, issue.recipients
698 697 issue.save!
699 698 assert !issue.recipients.include?(user.mail)
700 699 end
701 700
702 701 def test_recipients_should_not_include_users_that_cannot_view_the_issue
703 702 issue = Issue.find(12)
704 703 assert issue.recipients.include?(issue.author.mail)
705 704 # copy the issue to a private project
706 705 copy = issue.copy(:project_id => 5, :tracker_id => 2)
707 706 # author is not a member of project anymore
708 707 assert !copy.recipients.include?(copy.author.mail)
709 708 end
710 709
711 710 def test_recipients_should_include_the_assigned_group_members
712 711 group_member = User.generate_with_protected!
713 712 group = Group.generate!
714 713 group.users << group_member
715 714
716 715 issue = Issue.find(12)
717 716 issue.assigned_to = group
718 717 assert issue.recipients.include?(group_member.mail)
719 718 end
720 719
721 720 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
722 721 user = User.find(3)
723 722 issue = Issue.find(9)
724 723 Watcher.create!(:user => user, :watchable => issue)
725 724 assert issue.watched_by?(user)
726 725 assert !issue.watcher_recipients.include?(user.mail)
727 726 end
728 727
729 728 def test_issue_destroy
730 729 Issue.find(1).destroy
731 730 assert_nil Issue.find_by_id(1)
732 731 assert_nil TimeEntry.find_by_issue_id(1)
733 732 end
734 733
735 734 def test_blocked
736 735 blocked_issue = Issue.find(9)
737 736 blocking_issue = Issue.find(10)
738 737
739 738 assert blocked_issue.blocked?
740 739 assert !blocking_issue.blocked?
741 740 end
742 741
743 742 def test_blocked_issues_dont_allow_closed_statuses
744 743 blocked_issue = Issue.find(9)
745 744
746 745 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
747 746 assert !allowed_statuses.empty?
748 747 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
749 748 assert closed_statuses.empty?
750 749 end
751 750
752 751 def test_unblocked_issues_allow_closed_statuses
753 752 blocking_issue = Issue.find(10)
754 753
755 754 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
756 755 assert !allowed_statuses.empty?
757 756 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
758 757 assert !closed_statuses.empty?
759 758 end
760 759
761 760 def test_rescheduling_an_issue_should_reschedule_following_issue
762 761 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
763 762 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
764 763 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
765 764 assert_equal issue1.due_date + 1, issue2.reload.start_date
766 765
767 766 issue1.due_date = Date.today + 5
768 767 issue1.save!
769 768 assert_equal issue1.due_date + 1, issue2.reload.start_date
770 769 end
771 770
772 771 def test_overdue
773 772 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
774 773 assert !Issue.new(:due_date => Date.today).overdue?
775 774 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
776 775 assert !Issue.new(:due_date => nil).overdue?
777 776 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
778 777 end
779 778
780 779 context "#behind_schedule?" do
781 780 should "be false if the issue has no start_date" do
782 781 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
783 782 end
784 783
785 784 should "be false if the issue has no end_date" do
786 785 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
787 786 end
788 787
789 788 should "be false if the issue has more done than it's calendar time" do
790 789 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
791 790 end
792 791
793 792 should "be true if the issue hasn't been started at all" do
794 793 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
795 794 end
796 795
797 796 should "be true if the issue has used more calendar time than it's done ratio" do
798 797 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
799 798 end
800 799 end
801 800
802 801 context "#assignable_users" do
803 802 should "be Users" do
804 803 assert_kind_of User, Issue.find(1).assignable_users.first
805 804 end
806 805
807 806 should "include the issue author" do
808 807 project = Project.find(1)
809 808 non_project_member = User.generate!
810 809 issue = Issue.generate_for_project!(project, :author => non_project_member)
811 810
812 811 assert issue.assignable_users.include?(non_project_member)
813 812 end
814 813
815 814 should "include the current assignee" do
816 815 project = Project.find(1)
817 816 user = User.generate!
818 817 issue = Issue.generate_for_project!(project, :assigned_to => user)
819 818 user.lock!
820 819
821 820 assert Issue.find(issue.id).assignable_users.include?(user)
822 821 end
823 822
824 823 should "not show the issue author twice" do
825 824 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
826 825 assert_equal 2, assignable_user_ids.length
827 826
828 827 assignable_user_ids.each do |user_id|
829 828 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
830 829 end
831 830 end
832 831
833 832 context "with issue_group_assignment" do
834 833 should "include groups" do
835 834 issue = Issue.new(:project => Project.find(2))
836 835
837 836 with_settings :issue_group_assignment => '1' do
838 837 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
839 838 assert issue.assignable_users.include?(Group.find(11))
840 839 end
841 840 end
842 841 end
843 842
844 843 context "without issue_group_assignment" do
845 844 should "not include groups" do
846 845 issue = Issue.new(:project => Project.find(2))
847 846
848 847 with_settings :issue_group_assignment => '0' do
849 848 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
850 849 assert !issue.assignable_users.include?(Group.find(11))
851 850 end
852 851 end
853 852 end
854 853 end
855 854
856 855 def test_create_should_send_email_notification
857 856 ActionMailer::Base.deliveries.clear
858 857 issue = Issue.new(:project_id => 1, :tracker_id => 1,
859 858 :author_id => 3, :status_id => 1,
860 859 :priority => IssuePriority.all.first,
861 860 :subject => 'test_create', :estimated_hours => '1:30')
862 861
863 862 assert issue.save
864 863 assert_equal 1, ActionMailer::Base.deliveries.size
865 864 end
866 865
867 866 def test_stale_issue_should_not_send_email_notification
868 867 ActionMailer::Base.deliveries.clear
869 868 issue = Issue.find(1)
870 869 stale = Issue.find(1)
871 870
872 871 issue.init_journal(User.find(1))
873 872 issue.subject = 'Subjet update'
874 873 assert issue.save
875 874 assert_equal 1, ActionMailer::Base.deliveries.size
876 875 ActionMailer::Base.deliveries.clear
877 876
878 877 stale.init_journal(User.find(1))
879 878 stale.subject = 'Another subjet update'
880 879 assert_raise ActiveRecord::StaleObjectError do
881 880 stale.save
882 881 end
883 882 assert ActionMailer::Base.deliveries.empty?
884 883 end
885 884
886 885 def test_journalized_description
887 886 IssueCustomField.delete_all
888 887
889 888 i = Issue.first
890 889 old_description = i.description
891 890 new_description = "This is the new description"
892 891
893 892 i.init_journal(User.find(2))
894 893 i.description = new_description
895 894 assert_difference 'Journal.count', 1 do
896 895 assert_difference 'JournalDetail.count', 1 do
897 896 i.save!
898 897 end
899 898 end
900 899
901 900 detail = JournalDetail.first(:order => 'id DESC')
902 901 assert_equal i, detail.journal.journalized
903 902 assert_equal 'attr', detail.property
904 903 assert_equal 'description', detail.prop_key
905 904 assert_equal old_description, detail.old_value
906 905 assert_equal new_description, detail.value
907 906 end
908 907
909 908 def test_blank_descriptions_should_not_be_journalized
910 909 IssueCustomField.delete_all
911 910 Issue.update_all("description = NULL", "id=1")
912 911
913 912 i = Issue.find(1)
914 913 i.init_journal(User.find(2))
915 914 i.subject = "blank description"
916 915 i.description = "\r\n"
917 916
918 917 assert_difference 'Journal.count', 1 do
919 918 assert_difference 'JournalDetail.count', 1 do
920 919 i.save!
921 920 end
922 921 end
923 922 end
924 923
925 924 def test_description_eol_should_be_normalized
926 925 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
927 926 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
928 927 end
929 928
930 929 def test_saving_twice_should_not_duplicate_journal_details
931 930 i = Issue.find(:first)
932 931 i.init_journal(User.find(2), 'Some notes')
933 932 # initial changes
934 933 i.subject = 'New subject'
935 934 i.done_ratio = i.done_ratio + 10
936 935 assert_difference 'Journal.count' do
937 936 assert i.save
938 937 end
939 938 # 1 more change
940 939 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
941 940 assert_no_difference 'Journal.count' do
942 941 assert_difference 'JournalDetail.count', 1 do
943 942 i.save
944 943 end
945 944 end
946 945 # no more change
947 946 assert_no_difference 'Journal.count' do
948 947 assert_no_difference 'JournalDetail.count' do
949 948 i.save
950 949 end
951 950 end
952 951 end
953 952
954 953 def test_all_dependent_issues
955 954 IssueRelation.delete_all
956 955 assert IssueRelation.create!(:issue_from => Issue.find(1),
957 956 :issue_to => Issue.find(2),
958 957 :relation_type => IssueRelation::TYPE_PRECEDES)
959 958 assert IssueRelation.create!(:issue_from => Issue.find(2),
960 959 :issue_to => Issue.find(3),
961 960 :relation_type => IssueRelation::TYPE_PRECEDES)
962 961 assert IssueRelation.create!(:issue_from => Issue.find(3),
963 962 :issue_to => Issue.find(8),
964 963 :relation_type => IssueRelation::TYPE_PRECEDES)
965 964
966 965 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
967 966 end
968 967
969 968 def test_all_dependent_issues_with_persistent_circular_dependency
970 969 IssueRelation.delete_all
971 970 assert IssueRelation.create!(:issue_from => Issue.find(1),
972 971 :issue_to => Issue.find(2),
973 972 :relation_type => IssueRelation::TYPE_PRECEDES)
974 973 assert IssueRelation.create!(:issue_from => Issue.find(2),
975 974 :issue_to => Issue.find(3),
976 975 :relation_type => IssueRelation::TYPE_PRECEDES)
977 976 # Validation skipping
978 977 assert IssueRelation.new(:issue_from => Issue.find(3),
979 978 :issue_to => Issue.find(1),
980 979 :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
981 980
982 981 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
983 982 end
984 983
985 984 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
986 985 IssueRelation.delete_all
987 986 assert IssueRelation.create!(:issue_from => Issue.find(1),
988 987 :issue_to => Issue.find(2),
989 988 :relation_type => IssueRelation::TYPE_RELATES)
990 989 assert IssueRelation.create!(:issue_from => Issue.find(2),
991 990 :issue_to => Issue.find(3),
992 991 :relation_type => IssueRelation::TYPE_RELATES)
993 992 assert IssueRelation.create!(:issue_from => Issue.find(3),
994 993 :issue_to => Issue.find(8),
995 994 :relation_type => IssueRelation::TYPE_RELATES)
996 995 # Validation skipping
997 996 assert IssueRelation.new(:issue_from => Issue.find(8),
998 997 :issue_to => Issue.find(2),
999 998 :relation_type => IssueRelation::TYPE_RELATES).save(false)
1000 999 assert IssueRelation.new(:issue_from => Issue.find(3),
1001 1000 :issue_to => Issue.find(1),
1002 1001 :relation_type => IssueRelation::TYPE_RELATES).save(false)
1003 1002
1004 1003 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1005 1004 end
1006 1005
1007 1006 context "#done_ratio" do
1008 1007 setup do
1009 1008 @issue = Issue.find(1)
1010 1009 @issue_status = IssueStatus.find(1)
1011 1010 @issue_status.update_attribute(:default_done_ratio, 50)
1012 1011 @issue2 = Issue.find(2)
1013 1012 @issue_status2 = IssueStatus.find(2)
1014 1013 @issue_status2.update_attribute(:default_done_ratio, 0)
1015 1014 end
1016 1015
1017 1016 teardown do
1018 1017 Setting.issue_done_ratio = 'issue_field'
1019 1018 end
1020 1019
1021 1020 context "with Setting.issue_done_ratio using the issue_field" do
1022 1021 setup do
1023 1022 Setting.issue_done_ratio = 'issue_field'
1024 1023 end
1025 1024
1026 1025 should "read the issue's field" do
1027 1026 assert_equal 0, @issue.done_ratio
1028 1027 assert_equal 30, @issue2.done_ratio
1029 1028 end
1030 1029 end
1031 1030
1032 1031 context "with Setting.issue_done_ratio using the issue_status" do
1033 1032 setup do
1034 1033 Setting.issue_done_ratio = 'issue_status'
1035 1034 end
1036 1035
1037 1036 should "read the Issue Status's default done ratio" do
1038 1037 assert_equal 50, @issue.done_ratio
1039 1038 assert_equal 0, @issue2.done_ratio
1040 1039 end
1041 1040 end
1042 1041 end
1043 1042
1044 1043 context "#update_done_ratio_from_issue_status" do
1045 1044 setup do
1046 1045 @issue = Issue.find(1)
1047 1046 @issue_status = IssueStatus.find(1)
1048 1047 @issue_status.update_attribute(:default_done_ratio, 50)
1049 1048 @issue2 = Issue.find(2)
1050 1049 @issue_status2 = IssueStatus.find(2)
1051 1050 @issue_status2.update_attribute(:default_done_ratio, 0)
1052 1051 end
1053 1052
1054 1053 context "with Setting.issue_done_ratio using the issue_field" do
1055 1054 setup do
1056 1055 Setting.issue_done_ratio = 'issue_field'
1057 1056 end
1058 1057
1059 1058 should "not change the issue" do
1060 1059 @issue.update_done_ratio_from_issue_status
1061 1060 @issue2.update_done_ratio_from_issue_status
1062 1061
1063 1062 assert_equal 0, @issue.read_attribute(:done_ratio)
1064 1063 assert_equal 30, @issue2.read_attribute(:done_ratio)
1065 1064 end
1066 1065 end
1067 1066
1068 1067 context "with Setting.issue_done_ratio using the issue_status" do
1069 1068 setup do
1070 1069 Setting.issue_done_ratio = 'issue_status'
1071 1070 end
1072 1071
1073 1072 should "change the issue's done ratio" do
1074 1073 @issue.update_done_ratio_from_issue_status
1075 1074 @issue2.update_done_ratio_from_issue_status
1076 1075
1077 1076 assert_equal 50, @issue.read_attribute(:done_ratio)
1078 1077 assert_equal 0, @issue2.read_attribute(:done_ratio)
1079 1078 end
1080 1079 end
1081 1080 end
1082 1081
1083 1082 test "#by_tracker" do
1084 1083 User.current = User.anonymous
1085 1084 groups = Issue.by_tracker(Project.find(1))
1086 1085 assert_equal 3, groups.size
1087 1086 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1088 1087 end
1089 1088
1090 1089 test "#by_version" do
1091 1090 User.current = User.anonymous
1092 1091 groups = Issue.by_version(Project.find(1))
1093 1092 assert_equal 3, groups.size
1094 1093 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1095 1094 end
1096 1095
1097 1096 test "#by_priority" do
1098 1097 User.current = User.anonymous
1099 1098 groups = Issue.by_priority(Project.find(1))
1100 1099 assert_equal 4, groups.size
1101 1100 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1102 1101 end
1103 1102
1104 1103 test "#by_category" do
1105 1104 User.current = User.anonymous
1106 1105 groups = Issue.by_category(Project.find(1))
1107 1106 assert_equal 2, groups.size
1108 1107 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1109 1108 end
1110 1109
1111 1110 test "#by_assigned_to" do
1112 1111 User.current = User.anonymous
1113 1112 groups = Issue.by_assigned_to(Project.find(1))
1114 1113 assert_equal 2, groups.size
1115 1114 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1116 1115 end
1117 1116
1118 1117 test "#by_author" do
1119 1118 User.current = User.anonymous
1120 1119 groups = Issue.by_author(Project.find(1))
1121 1120 assert_equal 4, groups.size
1122 1121 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1123 1122 end
1124 1123
1125 1124 test "#by_subproject" do
1126 1125 User.current = User.anonymous
1127 1126 groups = Issue.by_subproject(Project.find(1))
1128 1127 # Private descendant not visible
1129 1128 assert_equal 1, groups.size
1130 1129 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1131 1130 end
1132 1131
1133 1132 context ".allowed_target_projects_on_move" do
1134 1133 should "return all active projects for admin users" do
1135 1134 User.current = User.find(1)
1136 1135 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1137 1136 end
1138 1137
1139 1138 should "return allowed projects for non admin users" do
1140 1139 User.current = User.find(2)
1141 1140 Role.non_member.remove_permission! :move_issues
1142 1141 assert_equal 3, Issue.allowed_target_projects_on_move.size
1143 1142
1144 1143 Role.non_member.add_permission! :move_issues
1145 1144 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1146 1145 end
1147 1146 end
1148 1147
1149 1148 def test_recently_updated_with_limit_scopes
1150 1149 #should return the last updated issue
1151 1150 assert_equal 1, Issue.recently_updated.with_limit(1).length
1152 1151 assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
1153 1152 end
1154 1153
1155 1154 def test_on_active_projects_scope
1156 1155 assert Project.find(2).archive
1157 1156
1158 1157 before = Issue.on_active_project.length
1159 1158 # test inclusion to results
1160 1159 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1161 1160 assert_equal before + 1, Issue.on_active_project.length
1162 1161
1163 1162 # Move to an archived project
1164 1163 issue.project = Project.find(2)
1165 1164 assert issue.save
1166 1165 assert_equal before, Issue.on_active_project.length
1167 1166 end
1168 1167
1169 1168 context "Issue#recipients" do
1170 1169 setup do
1171 1170 @project = Project.find(1)
1172 1171 @author = User.generate_with_protected!
1173 1172 @assignee = User.generate_with_protected!
1174 1173 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1175 1174 end
1176 1175
1177 1176 should "include project recipients" do
1178 1177 assert @project.recipients.present?
1179 1178 @project.recipients.each do |project_recipient|
1180 1179 assert @issue.recipients.include?(project_recipient)
1181 1180 end
1182 1181 end
1183 1182
1184 1183 should "include the author if the author is active" do
1185 1184 assert @issue.author, "No author set for Issue"
1186 1185 assert @issue.recipients.include?(@issue.author.mail)
1187 1186 end
1188 1187
1189 1188 should "include the assigned to user if the assigned to user is active" do
1190 1189 assert @issue.assigned_to, "No assigned_to set for Issue"
1191 1190 assert @issue.recipients.include?(@issue.assigned_to.mail)
1192 1191 end
1193 1192
1194 1193 should "not include users who opt out of all email" do
1195 1194 @author.update_attribute(:mail_notification, :none)
1196 1195
1197 1196 assert !@issue.recipients.include?(@issue.author.mail)
1198 1197 end
1199 1198
1200 1199 should "not include the issue author if they are only notified of assigned issues" do
1201 1200 @author.update_attribute(:mail_notification, :only_assigned)
1202 1201
1203 1202 assert !@issue.recipients.include?(@issue.author.mail)
1204 1203 end
1205 1204
1206 1205 should "not include the assigned user if they are only notified of owned issues" do
1207 1206 @assignee.update_attribute(:mail_notification, :only_owner)
1208 1207
1209 1208 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1210 1209 end
1211 1210
1212 1211 end
1213 1212 end
@@ -1,86 +1,88
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class TimeEntryActivityTest < ActiveSupport::TestCase
21 21 fixtures :enumerations, :time_entries
22 22
23 include Redmine::I18n
24
23 25 def test_should_be_an_enumeration
24 26 assert TimeEntryActivity.ancestors.include?(Enumeration)
25 27 end
26 28
27 29 def test_objects_count
28 30 assert_equal 3, TimeEntryActivity.find_by_name("Design").objects_count
29 31 assert_equal 2, TimeEntryActivity.find_by_name("Development").objects_count
30 32 end
31 33
32 34 def test_option_name
33 35 assert_equal :enumeration_activities, TimeEntryActivity.new.option_name
34 36 end
35 37
36 38 def test_create_with_custom_field
37 39 field = TimeEntryActivityCustomField.find_by_name('Billable')
38 40 e = TimeEntryActivity.new(:name => 'Custom Data')
39 41 e.custom_field_values = {field.id => "1"}
40 42 assert e.save
41 43
42 44 e.reload
43 45 assert_equal "1", e.custom_value_for(field).value
44 46 end
45 47
46 48 def test_create_without_required_custom_field_should_fail
49 set_language_if_valid 'en'
47 50 field = TimeEntryActivityCustomField.find_by_name('Billable')
48 51 field.update_attribute(:is_required, true)
49 52
50 53 e = TimeEntryActivity.new(:name => 'Custom Data')
51 54 assert !e.save
52 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
53 e.errors[:custom_values].to_s
55 assert_equal "Billable can't be blank", e.errors[:base].to_s
54 56 end
55 57
56 58 def test_create_with_required_custom_field_should_succeed
57 59 field = TimeEntryActivityCustomField.find_by_name('Billable')
58 60 field.update_attribute(:is_required, true)
59 61
60 62 e = TimeEntryActivity.new(:name => 'Custom Data')
61 63 e.custom_field_values = {field.id => "1"}
62 64 assert e.save
63 65 end
64 66
65 def test_update_issue_with_required_custom_field_change
67 def test_update_with_required_custom_field_change
68 set_language_if_valid 'en'
66 69 field = TimeEntryActivityCustomField.find_by_name('Billable')
67 70 field.update_attribute(:is_required, true)
68 71
69 72 e = TimeEntryActivity.find(10)
70 73 assert e.available_custom_fields.include?(field)
71 74 # No change to custom field, record can be saved
72 75 assert e.save
73 76 # Blanking custom field, save should fail
74 77 e.custom_field_values = {field.id => ""}
75 78 assert !e.save
76 assert e.errors[:custom_values]
79 assert_equal "Billable can't be blank", e.errors[:base].to_s
77 80
78 81 # Update custom field to valid value, save should succeed
79 82 e.custom_field_values = {field.id => "0"}
80 83 assert e.save
81 84 e.reload
82 85 assert_equal "0", e.custom_value_for(field).value
83 86 end
84
85 87 end
86 88
@@ -1,112 +1,139
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Acts
20 20 module Customizable
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 module ClassMethods
26 26 def acts_as_customizable(options = {})
27 27 return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
28 28 cattr_accessor :customizable_options
29 29 self.customizable_options = options
30 30 has_many :custom_values, :as => :customized,
31 31 :include => :custom_field,
32 32 :order => "#{CustomField.table_name}.position",
33 :dependent => :delete_all
34 before_validation { |customized| customized.custom_field_values if customized.new_record? }
35 # Trigger validation only if custom values were changed
36 validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? }
33 :dependent => :delete_all,
34 :validate => false
37 35 send :include, Redmine::Acts::Customizable::InstanceMethods
38 # Save custom values when saving the customized object
36 validate :validate_custom_field_values
39 37 after_save :save_custom_field_values
40 38 end
41 39 end
42 40
43 41 module InstanceMethods
44 42 def self.included(base)
45 43 base.extend ClassMethods
46 44 end
47 45
48 46 def available_custom_fields
49 47 CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'",
50 48 :order => 'position')
51 49 end
52 50
53 51 # Sets the values of the object's custom fields
54 52 # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}]
55 53 def custom_fields=(values)
56 54 values_to_hash = values.inject({}) do |hash, v|
57 55 v = v.stringify_keys
58 56 if v['id'] && v.has_key?('value')
59 57 hash[v['id']] = v['value']
60 58 end
61 59 hash
62 60 end
63 61 self.custom_field_values = values_to_hash
64 62 end
65 63
66 64 # Sets the values of the object's custom fields
67 65 # values is a hash like {'1' => 'foo', 2 => 'bar'}
68 66 def custom_field_values=(values)
69 @custom_field_values_changed = true
70 67 values = values.stringify_keys
71 custom_field_values.each do |custom_value|
72 custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
73 end if values.is_a?(Hash)
68
69 custom_field_values.each do |custom_field_value|
70 key = custom_field_value.custom_field_id.to_s
71 if values.has_key?(key)
72 value = values[key]
73 custom_field_value.value = value
74 end
75 end
76 @custom_field_values_changed = true
74 77 end
75 78
76 79 def custom_field_values
77 @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) }
80 @custom_field_values ||= available_custom_fields.collect do |field|
81 x = CustomFieldValue.new
82 x.custom_field = field
83 x.customized = self
84 cv = custom_values.detect { |v| v.custom_field == field }
85 cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil)
86 x.value = cv.value
87 x
88 end
78 89 end
79 90
80 91 def visible_custom_field_values
81 92 custom_field_values.select(&:visible?)
82 93 end
83 94
84 95 def custom_field_values_changed?
85 96 @custom_field_values_changed == true
86 97 end
87 98
88 99 def custom_value_for(c)
89 100 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
90 101 custom_values.detect {|v| v.custom_field_id == field_id }
91 102 end
92 103
104 def custom_field_value(c)
105 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
106 custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value)
107 end
108
109 def validate_custom_field_values
110 if new_record? || custom_field_values_changed?
111 custom_field_values.each(&:validate_value)
112 end
113 end
114
93 115 def save_custom_field_values
94 self.custom_values = custom_field_values
95 custom_field_values.each(&:save)
116 target_custom_values = []
117 custom_field_values.each do |custom_field_value|
118 target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field}
119 target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field)
120 target.value = custom_field_value.value
121 target_custom_values << target
122 end
123 self.custom_values = target_custom_values
124 custom_values.each(&:save)
96 125 @custom_field_values_changed = false
97 @custom_field_values = nil
126 true
98 127 end
99 128
100 129 def reset_custom_values!
101 130 @custom_field_values = nil
102 131 @custom_field_values_changed = true
103 values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
104 custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)}
105 132 end
106 133
107 134 module ClassMethods
108 135 end
109 136 end
110 137 end
111 138 end
112 139 end
General Comments 0
You need to be logged in to leave comments. Login now