##// END OF EJS Templates
Added observers to watch model objects for mail delivery instead of calling Mailer....
Eric Davis -
r2548:b4be8849c0de
parent child
Show More
@@ -0,0 +1,22
1 # redMine - project management software
2 # Copyright (C) 2006-2007 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 DocumentObserver < ActiveRecord::Observer
19 def after_create(document)
20 Mailer.deliver_document_added(document) if Setting.notified_events.include?('document_added')
21 end
22 end
@@ -0,0 +1,22
1 # redMine - project management software
2 # Copyright (C) 2006-2007 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 IssueObserver < ActiveRecord::Observer
19 def after_create(issue)
20 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
21 end
22 end
@@ -0,0 +1,22
1 # redMine - project management software
2 # Copyright (C) 2006-2007 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 JournalObserver < ActiveRecord::Observer
19 def after_create(journal)
20 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
21 end
22 end
@@ -0,0 +1,22
1 # redMine - project management software
2 # Copyright (C) 2006-2007 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 NewsObserver < ActiveRecord::Observer
19 def after_create(news)
20 Mailer.deliver_news_added(news) if Setting.notified_events.include?('news_added')
21 end
22 end
@@ -1,88 +1,87
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class DocumentsController < ApplicationController
18 class DocumentsController < ApplicationController
19 before_filter :find_project, :only => [:index, :new]
19 before_filter :find_project, :only => [:index, :new]
20 before_filter :find_document, :except => [:index, :new]
20 before_filter :find_document, :except => [:index, :new]
21 before_filter :authorize
21 before_filter :authorize
22
22
23 helper :attachments
23 helper :attachments
24
24
25 def index
25 def index
26 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
26 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
27 documents = @project.documents.find :all, :include => [:attachments, :category]
27 documents = @project.documents.find :all, :include => [:attachments, :category]
28 case @sort_by
28 case @sort_by
29 when 'date'
29 when 'date'
30 @grouped = documents.group_by {|d| d.created_on.to_date }
30 @grouped = documents.group_by {|d| d.created_on.to_date }
31 when 'title'
31 when 'title'
32 @grouped = documents.group_by {|d| d.title.first.upcase}
32 @grouped = documents.group_by {|d| d.title.first.upcase}
33 when 'author'
33 when 'author'
34 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
34 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
35 else
35 else
36 @grouped = documents.group_by(&:category)
36 @grouped = documents.group_by(&:category)
37 end
37 end
38 @document = @project.documents.build
38 @document = @project.documents.build
39 render :layout => false if request.xhr?
39 render :layout => false if request.xhr?
40 end
40 end
41
41
42 def show
42 def show
43 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
43 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
44 end
44 end
45
45
46 def new
46 def new
47 @document = @project.documents.build(params[:document])
47 @document = @project.documents.build(params[:document])
48 if request.post? and @document.save
48 if request.post? and @document.save
49 attach_files(@document, params[:attachments])
49 attach_files(@document, params[:attachments])
50 flash[:notice] = l(:notice_successful_create)
50 flash[:notice] = l(:notice_successful_create)
51 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
52 redirect_to :action => 'index', :project_id => @project
51 redirect_to :action => 'index', :project_id => @project
53 end
52 end
54 end
53 end
55
54
56 def edit
55 def edit
57 @categories = Enumeration.document_categories
56 @categories = Enumeration.document_categories
58 if request.post? and @document.update_attributes(params[:document])
57 if request.post? and @document.update_attributes(params[:document])
59 flash[:notice] = l(:notice_successful_update)
58 flash[:notice] = l(:notice_successful_update)
60 redirect_to :action => 'show', :id => @document
59 redirect_to :action => 'show', :id => @document
61 end
60 end
62 end
61 end
63
62
64 def destroy
63 def destroy
65 @document.destroy
64 @document.destroy
66 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
65 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
67 end
66 end
68
67
69 def add_attachment
68 def add_attachment
70 attachments = attach_files(@document, params[:attachments])
69 attachments = attach_files(@document, params[:attachments])
71 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
70 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
72 redirect_to :action => 'show', :id => @document
71 redirect_to :action => 'show', :id => @document
73 end
72 end
74
73
75 private
74 private
76 def find_project
75 def find_project
77 @project = Project.find(params[:project_id])
76 @project = Project.find(params[:project_id])
78 rescue ActiveRecord::RecordNotFound
77 rescue ActiveRecord::RecordNotFound
79 render_404
78 render_404
80 end
79 end
81
80
82 def find_document
81 def find_document
83 @document = Document.find(params[:id])
82 @document = Document.find(params[:id])
84 @project = @document.project
83 @project = @document.project
85 rescue ActiveRecord::RecordNotFound
84 rescue ActiveRecord::RecordNotFound
86 render_404
85 render_404
87 end
86 end
88 end
87 end
@@ -1,497 +1,492
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => :new
19 menu_item :new_issue, :only => :new
20
20
21 before_filter :find_issue, :only => [:show, :edit, :reply]
21 before_filter :find_issue, :only => [:show, :edit, :reply]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 accept_key_auth :index, :changes
26 accept_key_auth :index, :changes
27
27
28 helper :journals
28 helper :journals
29 helper :projects
29 helper :projects
30 include ProjectsHelper
30 include ProjectsHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33 helper :issue_relations
33 helper :issue_relations
34 include IssueRelationsHelper
34 include IssueRelationsHelper
35 helper :watchers
35 helper :watchers
36 include WatchersHelper
36 include WatchersHelper
37 helper :attachments
37 helper :attachments
38 include AttachmentsHelper
38 include AttachmentsHelper
39 helper :queries
39 helper :queries
40 helper :sort
40 helper :sort
41 include SortHelper
41 include SortHelper
42 include IssuesHelper
42 include IssuesHelper
43 helper :timelog
43 helper :timelog
44 include Redmine::Export::PDF
44 include Redmine::Export::PDF
45
45
46 def index
46 def index
47 retrieve_query
47 retrieve_query
48 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
48 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
50
50
51 if @query.valid?
51 if @query.valid?
52 limit = per_page_option
52 limit = per_page_option
53 respond_to do |format|
53 respond_to do |format|
54 format.html { }
54 format.html { }
55 format.atom { }
55 format.atom { }
56 format.csv { limit = Setting.issues_export_limit.to_i }
56 format.csv { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
58 end
58 end
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61 @issues = Issue.find :all, :order => sort_clause,
61 @issues = Issue.find :all, :order => sort_clause,
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63 :conditions => @query.statement,
63 :conditions => @query.statement,
64 :limit => limit,
64 :limit => limit,
65 :offset => @issue_pages.current.offset
65 :offset => @issue_pages.current.offset
66 respond_to do |format|
66 respond_to do |format|
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
71 end
71 end
72 else
72 else
73 # Send html if the query is not valid
73 # Send html if the query is not valid
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
75 end
75 end
76 rescue ActiveRecord::RecordNotFound
76 rescue ActiveRecord::RecordNotFound
77 render_404
77 render_404
78 end
78 end
79
79
80 def changes
80 def changes
81 retrieve_query
81 retrieve_query
82 sort_init 'id', 'desc'
82 sort_init 'id', 'desc'
83 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
83 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
84
84
85 if @query.valid?
85 if @query.valid?
86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
87 :conditions => @query.statement,
87 :conditions => @query.statement,
88 :limit => 25,
88 :limit => 25,
89 :order => "#{Journal.table_name}.created_on DESC"
89 :order => "#{Journal.table_name}.created_on DESC"
90 end
90 end
91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
92 render :layout => false, :content_type => 'application/atom+xml'
92 render :layout => false, :content_type => 'application/atom+xml'
93 rescue ActiveRecord::RecordNotFound
93 rescue ActiveRecord::RecordNotFound
94 render_404
94 render_404
95 end
95 end
96
96
97 def show
97 def show
98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 @journals.each_with_index {|j,i| j.indice = i+1}
99 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 @changesets = @issue.changesets
101 @changesets = @issue.changesets
102 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
102 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
103 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
103 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
104 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
104 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
105 @priorities = Enumeration.priorities
105 @priorities = Enumeration.priorities
106 @time_entry = TimeEntry.new
106 @time_entry = TimeEntry.new
107 respond_to do |format|
107 respond_to do |format|
108 format.html { render :template => 'issues/show.rhtml' }
108 format.html { render :template => 'issues/show.rhtml' }
109 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
109 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
110 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
110 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
111 end
111 end
112 end
112 end
113
113
114 # Add a new issue
114 # Add a new issue
115 # The new issue will be created from an existing one if copy_from parameter is given
115 # The new issue will be created from an existing one if copy_from parameter is given
116 def new
116 def new
117 @issue = Issue.new
117 @issue = Issue.new
118 @issue.copy_from(params[:copy_from]) if params[:copy_from]
118 @issue.copy_from(params[:copy_from]) if params[:copy_from]
119 @issue.project = @project
119 @issue.project = @project
120 # Tracker must be set before custom field values
120 # Tracker must be set before custom field values
121 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
121 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
122 if @issue.tracker.nil?
122 if @issue.tracker.nil?
123 render_error 'No tracker is associated to this project. Please check the Project settings.'
123 render_error 'No tracker is associated to this project. Please check the Project settings.'
124 return
124 return
125 end
125 end
126 if params[:issue].is_a?(Hash)
126 if params[:issue].is_a?(Hash)
127 @issue.attributes = params[:issue]
127 @issue.attributes = params[:issue]
128 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
128 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
129 end
129 end
130 @issue.author = User.current
130 @issue.author = User.current
131
131
132 default_status = IssueStatus.default
132 default_status = IssueStatus.default
133 unless default_status
133 unless default_status
134 render_error 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
134 render_error 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
135 return
135 return
136 end
136 end
137 @issue.status = default_status
137 @issue.status = default_status
138 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
138 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
139
139
140 if request.get? || request.xhr?
140 if request.get? || request.xhr?
141 @issue.start_date ||= Date.today
141 @issue.start_date ||= Date.today
142 else
142 else
143 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
143 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
144 # Check that the user is allowed to apply the requested status
144 # Check that the user is allowed to apply the requested status
145 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
145 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
146 if @issue.save
146 if @issue.save
147 attach_files(@issue, params[:attachments])
147 attach_files(@issue, params[:attachments])
148 flash[:notice] = l(:notice_successful_create)
148 flash[:notice] = l(:notice_successful_create)
149 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
150 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
149 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
151 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
150 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
152 { :action => 'show', :id => @issue })
151 { :action => 'show', :id => @issue })
153 return
152 return
154 end
153 end
155 end
154 end
156 @priorities = Enumeration.priorities
155 @priorities = Enumeration.priorities
157 render :layout => !request.xhr?
156 render :layout => !request.xhr?
158 end
157 end
159
158
160 # Attributes that can be updated on workflow transition (without :edit permission)
159 # Attributes that can be updated on workflow transition (without :edit permission)
161 # TODO: make it configurable (at least per role)
160 # TODO: make it configurable (at least per role)
162 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
161 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
163
162
164 def edit
163 def edit
165 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
164 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
166 @priorities = Enumeration.priorities
165 @priorities = Enumeration.priorities
167 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
166 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
168 @time_entry = TimeEntry.new
167 @time_entry = TimeEntry.new
169
168
170 @notes = params[:notes]
169 @notes = params[:notes]
171 journal = @issue.init_journal(User.current, @notes)
170 journal = @issue.init_journal(User.current, @notes)
172 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
171 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
173 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
172 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
174 attrs = params[:issue].dup
173 attrs = params[:issue].dup
175 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
174 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
176 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
175 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
177 @issue.attributes = attrs
176 @issue.attributes = attrs
178 end
177 end
179
178
180 if request.post?
179 if request.post?
181 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
180 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
182 @time_entry.attributes = params[:time_entry]
181 @time_entry.attributes = params[:time_entry]
183 attachments = attach_files(@issue, params[:attachments])
182 attachments = attach_files(@issue, params[:attachments])
184 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
183 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
185
184
186 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
185 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
187
186
188 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
187 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
189 # Log spend time
188 # Log spend time
190 if User.current.allowed_to?(:log_time, @project)
189 if User.current.allowed_to?(:log_time, @project)
191 @time_entry.save
190 @time_entry.save
192 end
191 end
193 if !journal.new_record?
192 if !journal.new_record?
194 # Only send notification if something was actually changed
193 # Only send notification if something was actually changed
195 flash[:notice] = l(:notice_successful_update)
194 flash[:notice] = l(:notice_successful_update)
196 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
197 end
195 end
198 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
196 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
199 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
197 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
200 end
198 end
201 end
199 end
202 rescue ActiveRecord::StaleObjectError
200 rescue ActiveRecord::StaleObjectError
203 # Optimistic locking exception
201 # Optimistic locking exception
204 flash.now[:error] = l(:notice_locking_conflict)
202 flash.now[:error] = l(:notice_locking_conflict)
205 end
203 end
206
204
207 def reply
205 def reply
208 journal = Journal.find(params[:journal_id]) if params[:journal_id]
206 journal = Journal.find(params[:journal_id]) if params[:journal_id]
209 if journal
207 if journal
210 user = journal.user
208 user = journal.user
211 text = journal.notes
209 text = journal.notes
212 else
210 else
213 user = @issue.author
211 user = @issue.author
214 text = @issue.description
212 text = @issue.description
215 end
213 end
216 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
214 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
217 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
215 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
218 render(:update) { |page|
216 render(:update) { |page|
219 page.<< "$('notes').value = \"#{content}\";"
217 page.<< "$('notes').value = \"#{content}\";"
220 page.show 'update'
218 page.show 'update'
221 page << "Form.Element.focus('notes');"
219 page << "Form.Element.focus('notes');"
222 page << "Element.scrollTo('update');"
220 page << "Element.scrollTo('update');"
223 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
221 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
224 }
222 }
225 end
223 end
226
224
227 # Bulk edit a set of issues
225 # Bulk edit a set of issues
228 def bulk_edit
226 def bulk_edit
229 if request.post?
227 if request.post?
230 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
228 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
231 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
229 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
232 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
230 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
233 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
231 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
234 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
232 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
235 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
233 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
236
234
237 unsaved_issue_ids = []
235 unsaved_issue_ids = []
238 @issues.each do |issue|
236 @issues.each do |issue|
239 journal = issue.init_journal(User.current, params[:notes])
237 journal = issue.init_journal(User.current, params[:notes])
240 issue.priority = priority if priority
238 issue.priority = priority if priority
241 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
239 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
242 issue.category = category if category || params[:category_id] == 'none'
240 issue.category = category if category || params[:category_id] == 'none'
243 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
241 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
244 issue.start_date = params[:start_date] unless params[:start_date].blank?
242 issue.start_date = params[:start_date] unless params[:start_date].blank?
245 issue.due_date = params[:due_date] unless params[:due_date].blank?
243 issue.due_date = params[:due_date] unless params[:due_date].blank?
246 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
244 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
247 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
245 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
248 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
246 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
249 # Don't save any change to the issue if the user is not authorized to apply the requested status
247 # Don't save any change to the issue if the user is not authorized to apply the requested status
250 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
248 unless (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
251 # Send notification for each issue (if changed)
252 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
253 else
254 # Keep unsaved issue ids to display them in flash error
249 # Keep unsaved issue ids to display them in flash error
255 unsaved_issue_ids << issue.id
250 unsaved_issue_ids << issue.id
256 end
251 end
257 end
252 end
258 if unsaved_issue_ids.empty?
253 if unsaved_issue_ids.empty?
259 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
254 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
260 else
255 else
261 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
256 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
262 :total => @issues.size,
257 :total => @issues.size,
263 :ids => '#' + unsaved_issue_ids.join(', #'))
258 :ids => '#' + unsaved_issue_ids.join(', #'))
264 end
259 end
265 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
260 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
266 return
261 return
267 end
262 end
268 # Find potential statuses the user could be allowed to switch issues to
263 # Find potential statuses the user could be allowed to switch issues to
269 @available_statuses = Workflow.find(:all, :include => :new_status,
264 @available_statuses = Workflow.find(:all, :include => :new_status,
270 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
265 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
271 @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
266 @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
272 end
267 end
273
268
274 def move
269 def move
275 @allowed_projects = []
270 @allowed_projects = []
276 # find projects to which the user is allowed to move the issue
271 # find projects to which the user is allowed to move the issue
277 if User.current.admin?
272 if User.current.admin?
278 # admin is allowed to move issues to any active (visible) project
273 # admin is allowed to move issues to any active (visible) project
279 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
274 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
280 else
275 else
281 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
276 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
282 end
277 end
283 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
278 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
284 @target_project ||= @project
279 @target_project ||= @project
285 @trackers = @target_project.trackers
280 @trackers = @target_project.trackers
286 if request.post?
281 if request.post?
287 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
282 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
288 unsaved_issue_ids = []
283 unsaved_issue_ids = []
289 @issues.each do |issue|
284 @issues.each do |issue|
290 issue.init_journal(User.current)
285 issue.init_journal(User.current)
291 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
286 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
292 end
287 end
293 if unsaved_issue_ids.empty?
288 if unsaved_issue_ids.empty?
294 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
289 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
295 else
290 else
296 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
291 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
297 :total => @issues.size,
292 :total => @issues.size,
298 :ids => '#' + unsaved_issue_ids.join(', #'))
293 :ids => '#' + unsaved_issue_ids.join(', #'))
299 end
294 end
300 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
295 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
301 return
296 return
302 end
297 end
303 render :layout => false if request.xhr?
298 render :layout => false if request.xhr?
304 end
299 end
305
300
306 def destroy
301 def destroy
307 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
302 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
308 if @hours > 0
303 if @hours > 0
309 case params[:todo]
304 case params[:todo]
310 when 'destroy'
305 when 'destroy'
311 # nothing to do
306 # nothing to do
312 when 'nullify'
307 when 'nullify'
313 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
308 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
314 when 'reassign'
309 when 'reassign'
315 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
310 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
316 if reassign_to.nil?
311 if reassign_to.nil?
317 flash.now[:error] = l(:error_issue_not_found_in_project)
312 flash.now[:error] = l(:error_issue_not_found_in_project)
318 return
313 return
319 else
314 else
320 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
315 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
321 end
316 end
322 else
317 else
323 # display the destroy form
318 # display the destroy form
324 return
319 return
325 end
320 end
326 end
321 end
327 @issues.each(&:destroy)
322 @issues.each(&:destroy)
328 redirect_to :action => 'index', :project_id => @project
323 redirect_to :action => 'index', :project_id => @project
329 end
324 end
330
325
331 def gantt
326 def gantt
332 @gantt = Redmine::Helpers::Gantt.new(params)
327 @gantt = Redmine::Helpers::Gantt.new(params)
333 retrieve_query
328 retrieve_query
334 if @query.valid?
329 if @query.valid?
335 events = []
330 events = []
336 # Issues that have start and due dates
331 # Issues that have start and due dates
337 events += Issue.find(:all,
332 events += Issue.find(:all,
338 :order => "start_date, due_date",
333 :order => "start_date, due_date",
339 :include => [:tracker, :status, :assigned_to, :priority, :project],
334 :include => [:tracker, :status, :assigned_to, :priority, :project],
340 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
335 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
341 )
336 )
342 # Issues that don't have a due date but that are assigned to a version with a date
337 # Issues that don't have a due date but that are assigned to a version with a date
343 events += Issue.find(:all,
338 events += Issue.find(:all,
344 :order => "start_date, effective_date",
339 :order => "start_date, effective_date",
345 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
340 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
346 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
341 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
347 )
342 )
348 # Versions
343 # Versions
349 events += Version.find(:all, :include => :project,
344 events += Version.find(:all, :include => :project,
350 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
345 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
351
346
352 @gantt.events = events
347 @gantt.events = events
353 end
348 end
354
349
355 respond_to do |format|
350 respond_to do |format|
356 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
351 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
357 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
352 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
358 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
353 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
359 end
354 end
360 end
355 end
361
356
362 def calendar
357 def calendar
363 if params[:year] and params[:year].to_i > 1900
358 if params[:year] and params[:year].to_i > 1900
364 @year = params[:year].to_i
359 @year = params[:year].to_i
365 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
360 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
366 @month = params[:month].to_i
361 @month = params[:month].to_i
367 end
362 end
368 end
363 end
369 @year ||= Date.today.year
364 @year ||= Date.today.year
370 @month ||= Date.today.month
365 @month ||= Date.today.month
371
366
372 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
367 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
373 retrieve_query
368 retrieve_query
374 if @query.valid?
369 if @query.valid?
375 events = []
370 events = []
376 events += Issue.find(:all,
371 events += Issue.find(:all,
377 :include => [:tracker, :status, :assigned_to, :priority, :project],
372 :include => [:tracker, :status, :assigned_to, :priority, :project],
378 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
373 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
379 )
374 )
380 events += Version.find(:all, :include => :project,
375 events += Version.find(:all, :include => :project,
381 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
376 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
382
377
383 @calendar.events = events
378 @calendar.events = events
384 end
379 end
385
380
386 render :layout => false if request.xhr?
381 render :layout => false if request.xhr?
387 end
382 end
388
383
389 def context_menu
384 def context_menu
390 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
385 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
391 if (@issues.size == 1)
386 if (@issues.size == 1)
392 @issue = @issues.first
387 @issue = @issues.first
393 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
388 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
394 end
389 end
395 projects = @issues.collect(&:project).compact.uniq
390 projects = @issues.collect(&:project).compact.uniq
396 @project = projects.first if projects.size == 1
391 @project = projects.first if projects.size == 1
397
392
398 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
393 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
399 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
394 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
400 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
395 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
401 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
396 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
402 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
397 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
403 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
398 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
404 }
399 }
405 if @project
400 if @project
406 @assignables = @project.assignable_users
401 @assignables = @project.assignable_users
407 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
402 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
408 end
403 end
409
404
410 @priorities = Enumeration.priorities.reverse
405 @priorities = Enumeration.priorities.reverse
411 @statuses = IssueStatus.find(:all, :order => 'position')
406 @statuses = IssueStatus.find(:all, :order => 'position')
412 @back = request.env['HTTP_REFERER']
407 @back = request.env['HTTP_REFERER']
413
408
414 render :layout => false
409 render :layout => false
415 end
410 end
416
411
417 def update_form
412 def update_form
418 @issue = Issue.new(params[:issue])
413 @issue = Issue.new(params[:issue])
419 render :action => :new, :layout => false
414 render :action => :new, :layout => false
420 end
415 end
421
416
422 def preview
417 def preview
423 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
418 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
424 @attachements = @issue.attachments if @issue
419 @attachements = @issue.attachments if @issue
425 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
420 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
426 render :partial => 'common/preview'
421 render :partial => 'common/preview'
427 end
422 end
428
423
429 private
424 private
430 def find_issue
425 def find_issue
431 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
426 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
432 @project = @issue.project
427 @project = @issue.project
433 rescue ActiveRecord::RecordNotFound
428 rescue ActiveRecord::RecordNotFound
434 render_404
429 render_404
435 end
430 end
436
431
437 # Filter for bulk operations
432 # Filter for bulk operations
438 def find_issues
433 def find_issues
439 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
434 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
440 raise ActiveRecord::RecordNotFound if @issues.empty?
435 raise ActiveRecord::RecordNotFound if @issues.empty?
441 projects = @issues.collect(&:project).compact.uniq
436 projects = @issues.collect(&:project).compact.uniq
442 if projects.size == 1
437 if projects.size == 1
443 @project = projects.first
438 @project = projects.first
444 else
439 else
445 # TODO: let users bulk edit/move/destroy issues from different projects
440 # TODO: let users bulk edit/move/destroy issues from different projects
446 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
441 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
447 end
442 end
448 rescue ActiveRecord::RecordNotFound
443 rescue ActiveRecord::RecordNotFound
449 render_404
444 render_404
450 end
445 end
451
446
452 def find_project
447 def find_project
453 @project = Project.find(params[:project_id])
448 @project = Project.find(params[:project_id])
454 rescue ActiveRecord::RecordNotFound
449 rescue ActiveRecord::RecordNotFound
455 render_404
450 render_404
456 end
451 end
457
452
458 def find_optional_project
453 def find_optional_project
459 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
454 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
460 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
455 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
461 allowed ? true : deny_access
456 allowed ? true : deny_access
462 rescue ActiveRecord::RecordNotFound
457 rescue ActiveRecord::RecordNotFound
463 render_404
458 render_404
464 end
459 end
465
460
466 # Retrieve query from session or build a new query
461 # Retrieve query from session or build a new query
467 def retrieve_query
462 def retrieve_query
468 if !params[:query_id].blank?
463 if !params[:query_id].blank?
469 cond = "project_id IS NULL"
464 cond = "project_id IS NULL"
470 cond << " OR project_id = #{@project.id}" if @project
465 cond << " OR project_id = #{@project.id}" if @project
471 @query = Query.find(params[:query_id], :conditions => cond)
466 @query = Query.find(params[:query_id], :conditions => cond)
472 @query.project = @project
467 @query.project = @project
473 session[:query] = {:id => @query.id, :project_id => @query.project_id}
468 session[:query] = {:id => @query.id, :project_id => @query.project_id}
474 sort_clear
469 sort_clear
475 else
470 else
476 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
471 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
477 # Give it a name, required to be valid
472 # Give it a name, required to be valid
478 @query = Query.new(:name => "_")
473 @query = Query.new(:name => "_")
479 @query.project = @project
474 @query.project = @project
480 if params[:fields] and params[:fields].is_a? Array
475 if params[:fields] and params[:fields].is_a? Array
481 params[:fields].each do |field|
476 params[:fields].each do |field|
482 @query.add_filter(field, params[:operators][field], params[:values][field])
477 @query.add_filter(field, params[:operators][field], params[:values][field])
483 end
478 end
484 else
479 else
485 @query.available_filters.keys.each do |field|
480 @query.available_filters.keys.each do |field|
486 @query.add_short_filter(field, params[field]) if params[field]
481 @query.add_short_filter(field, params[field]) if params[field]
487 end
482 end
488 end
483 end
489 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
484 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
490 else
485 else
491 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
486 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
492 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
487 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
493 @query.project = @project
488 @query.project = @project
494 end
489 end
495 end
490 end
496 end
491 end
497 end
492 end
@@ -1,108 +1,107
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class NewsController < ApplicationController
18 class NewsController < ApplicationController
19 before_filter :find_news, :except => [:new, :index, :preview]
19 before_filter :find_news, :except => [:new, :index, :preview]
20 before_filter :find_project, :only => [:new, :preview]
20 before_filter :find_project, :only => [:new, :preview]
21 before_filter :authorize, :except => [:index, :preview]
21 before_filter :authorize, :except => [:index, :preview]
22 before_filter :find_optional_project, :only => :index
22 before_filter :find_optional_project, :only => :index
23 accept_key_auth :index
23 accept_key_auth :index
24
24
25 def index
25 def index
26 @news_pages, @newss = paginate :news,
26 @news_pages, @newss = paginate :news,
27 :per_page => 10,
27 :per_page => 10,
28 :conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
28 :conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
29 :include => [:author, :project],
29 :include => [:author, :project],
30 :order => "#{News.table_name}.created_on DESC"
30 :order => "#{News.table_name}.created_on DESC"
31 respond_to do |format|
31 respond_to do |format|
32 format.html { render :layout => false if request.xhr? }
32 format.html { render :layout => false if request.xhr? }
33 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
33 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
34 end
34 end
35 end
35 end
36
36
37 def show
37 def show
38 @comments = @news.comments
38 @comments = @news.comments
39 @comments.reverse! if User.current.wants_comments_in_reverse_order?
39 @comments.reverse! if User.current.wants_comments_in_reverse_order?
40 end
40 end
41
41
42 def new
42 def new
43 @news = News.new(:project => @project, :author => User.current)
43 @news = News.new(:project => @project, :author => User.current)
44 if request.post?
44 if request.post?
45 @news.attributes = params[:news]
45 @news.attributes = params[:news]
46 if @news.save
46 if @news.save
47 flash[:notice] = l(:notice_successful_create)
47 flash[:notice] = l(:notice_successful_create)
48 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
49 redirect_to :controller => 'news', :action => 'index', :project_id => @project
48 redirect_to :controller => 'news', :action => 'index', :project_id => @project
50 end
49 end
51 end
50 end
52 end
51 end
53
52
54 def edit
53 def edit
55 if request.post? and @news.update_attributes(params[:news])
54 if request.post? and @news.update_attributes(params[:news])
56 flash[:notice] = l(:notice_successful_update)
55 flash[:notice] = l(:notice_successful_update)
57 redirect_to :action => 'show', :id => @news
56 redirect_to :action => 'show', :id => @news
58 end
57 end
59 end
58 end
60
59
61 def add_comment
60 def add_comment
62 @comment = Comment.new(params[:comment])
61 @comment = Comment.new(params[:comment])
63 @comment.author = User.current
62 @comment.author = User.current
64 if @news.comments << @comment
63 if @news.comments << @comment
65 flash[:notice] = l(:label_comment_added)
64 flash[:notice] = l(:label_comment_added)
66 redirect_to :action => 'show', :id => @news
65 redirect_to :action => 'show', :id => @news
67 else
66 else
68 render :action => 'show'
67 render :action => 'show'
69 end
68 end
70 end
69 end
71
70
72 def destroy_comment
71 def destroy_comment
73 @news.comments.find(params[:comment_id]).destroy
72 @news.comments.find(params[:comment_id]).destroy
74 redirect_to :action => 'show', :id => @news
73 redirect_to :action => 'show', :id => @news
75 end
74 end
76
75
77 def destroy
76 def destroy
78 @news.destroy
77 @news.destroy
79 redirect_to :action => 'index', :project_id => @project
78 redirect_to :action => 'index', :project_id => @project
80 end
79 end
81
80
82 def preview
81 def preview
83 @text = (params[:news] ? params[:news][:description] : nil)
82 @text = (params[:news] ? params[:news][:description] : nil)
84 render :partial => 'common/preview'
83 render :partial => 'common/preview'
85 end
84 end
86
85
87 private
86 private
88 def find_news
87 def find_news
89 @news = News.find(params[:id])
88 @news = News.find(params[:id])
90 @project = @news.project
89 @project = @news.project
91 rescue ActiveRecord::RecordNotFound
90 rescue ActiveRecord::RecordNotFound
92 render_404
91 render_404
93 end
92 end
94
93
95 def find_project
94 def find_project
96 @project = Project.find(params[:project_id])
95 @project = Project.find(params[:project_id])
97 rescue ActiveRecord::RecordNotFound
96 rescue ActiveRecord::RecordNotFound
98 render_404
97 render_404
99 end
98 end
100
99
101 def find_optional_project
100 def find_optional_project
102 return true unless params[:project_id]
101 return true unless params[:project_id]
103 @project = Project.find(params[:project_id])
102 @project = Project.find(params[:project_id])
104 authorize
103 authorize
105 rescue ActiveRecord::RecordNotFound
104 rescue ActiveRecord::RecordNotFound
106 render_404
105 render_404
107 end
106 end
108 end
107 end
@@ -1,169 +1,168
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'iconv'
18 require 'iconv'
19
19
20 class Changeset < ActiveRecord::Base
20 class Changeset < ActiveRecord::Base
21 belongs_to :repository
21 belongs_to :repository
22 belongs_to :user
22 belongs_to :user
23 has_many :changes, :dependent => :delete_all
23 has_many :changes, :dependent => :delete_all
24 has_and_belongs_to_many :issues
24 has_and_belongs_to_many :issues
25
25
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 :description => :long_comments,
27 :description => :long_comments,
28 :datetime => :committed_on,
28 :datetime => :committed_on,
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
30
30
31 acts_as_searchable :columns => 'comments',
31 acts_as_searchable :columns => 'comments',
32 :include => {:repository => :project},
32 :include => {:repository => :project},
33 :project_key => "#{Repository.table_name}.project_id",
33 :project_key => "#{Repository.table_name}.project_id",
34 :date_column => 'committed_on'
34 :date_column => 'committed_on'
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 :author_key => :user_id,
37 :author_key => :user_id,
38 :find_options => {:include => {:repository => :project}}
38 :find_options => {:include => {:repository => :project}}
39
39
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
41 validates_uniqueness_of :revision, :scope => :repository_id
41 validates_uniqueness_of :revision, :scope => :repository_id
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
43
43
44 def revision=(r)
44 def revision=(r)
45 write_attribute :revision, (r.nil? ? nil : r.to_s)
45 write_attribute :revision, (r.nil? ? nil : r.to_s)
46 end
46 end
47
47
48 def comments=(comment)
48 def comments=(comment)
49 write_attribute(:comments, Changeset.normalize_comments(comment))
49 write_attribute(:comments, Changeset.normalize_comments(comment))
50 end
50 end
51
51
52 def committed_on=(date)
52 def committed_on=(date)
53 self.commit_date = date
53 self.commit_date = date
54 super
54 super
55 end
55 end
56
56
57 def project
57 def project
58 repository.project
58 repository.project
59 end
59 end
60
60
61 def author
61 def author
62 user || committer.to_s.split('<').first
62 user || committer.to_s.split('<').first
63 end
63 end
64
64
65 def before_create
65 def before_create
66 self.user = repository.find_committer_user(committer)
66 self.user = repository.find_committer_user(committer)
67 end
67 end
68
68
69 def after_create
69 def after_create
70 scan_comment_for_issue_ids
70 scan_comment_for_issue_ids
71 end
71 end
72 require 'pp'
72 require 'pp'
73
73
74 def scan_comment_for_issue_ids
74 def scan_comment_for_issue_ids
75 return if comments.blank?
75 return if comments.blank?
76 # keywords used to reference issues
76 # keywords used to reference issues
77 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
77 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
78 # keywords used to fix issues
78 # keywords used to fix issues
79 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
79 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
80 # status and optional done ratio applied
80 # status and optional done ratio applied
81 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
81 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
82 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
82 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
83
83
84 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
84 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
85 return if kw_regexp.blank?
85 return if kw_regexp.blank?
86
86
87 referenced_issues = []
87 referenced_issues = []
88
88
89 if ref_keywords.delete('*')
89 if ref_keywords.delete('*')
90 # find any issue ID in the comments
90 # find any issue ID in the comments
91 target_issue_ids = []
91 target_issue_ids = []
92 comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
92 comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
93 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
93 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
94 end
94 end
95
95
96 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
96 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
97 action = match[0]
97 action = match[0]
98 target_issue_ids = match[1].scan(/\d+/)
98 target_issue_ids = match[1].scan(/\d+/)
99 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
99 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
100 if fix_status && fix_keywords.include?(action.downcase)
100 if fix_status && fix_keywords.include?(action.downcase)
101 # update status of issues
101 # update status of issues
102 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
102 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
103 target_issues.each do |issue|
103 target_issues.each do |issue|
104 # the issue may have been updated by the closure of another one (eg. duplicate)
104 # the issue may have been updated by the closure of another one (eg. duplicate)
105 issue.reload
105 issue.reload
106 # don't change the status is the issue is closed
106 # don't change the status is the issue is closed
107 next if issue.status.is_closed?
107 next if issue.status.is_closed?
108 csettext = "r#{self.revision}"
108 csettext = "r#{self.revision}"
109 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
109 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
110 csettext = "commit:\"#{self.scmid}\""
110 csettext = "commit:\"#{self.scmid}\""
111 end
111 end
112 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
112 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
113 issue.status = fix_status
113 issue.status = fix_status
114 issue.done_ratio = done_ratio if done_ratio
114 issue.done_ratio = done_ratio if done_ratio
115 issue.save
115 issue.save
116 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
117 end
116 end
118 end
117 end
119 referenced_issues += target_issues
118 referenced_issues += target_issues
120 end
119 end
121
120
122 self.issues = referenced_issues.uniq
121 self.issues = referenced_issues.uniq
123 end
122 end
124
123
125 def short_comments
124 def short_comments
126 @short_comments || split_comments.first
125 @short_comments || split_comments.first
127 end
126 end
128
127
129 def long_comments
128 def long_comments
130 @long_comments || split_comments.last
129 @long_comments || split_comments.last
131 end
130 end
132
131
133 # Returns the previous changeset
132 # Returns the previous changeset
134 def previous
133 def previous
135 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
134 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
136 end
135 end
137
136
138 # Returns the next changeset
137 # Returns the next changeset
139 def next
138 def next
140 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
139 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
141 end
140 end
142
141
143 # Strips and reencodes a commit log before insertion into the database
142 # Strips and reencodes a commit log before insertion into the database
144 def self.normalize_comments(str)
143 def self.normalize_comments(str)
145 to_utf8(str.to_s.strip)
144 to_utf8(str.to_s.strip)
146 end
145 end
147
146
148 private
147 private
149
148
150 def split_comments
149 def split_comments
151 comments =~ /\A(.+?)\r?\n(.*)$/m
150 comments =~ /\A(.+?)\r?\n(.*)$/m
152 @short_comments = $1 || comments
151 @short_comments = $1 || comments
153 @long_comments = $2.to_s.strip
152 @long_comments = $2.to_s.strip
154 return @short_comments, @long_comments
153 return @short_comments, @long_comments
155 end
154 end
156
155
157 def self.to_utf8(str)
156 def self.to_utf8(str)
158 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
157 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
159 encoding = Setting.commit_logs_encoding.to_s.strip
158 encoding = Setting.commit_logs_encoding.to_s.strip
160 unless encoding.blank? || encoding == 'UTF-8'
159 unless encoding.blank? || encoding == 'UTF-8'
161 begin
160 begin
162 return Iconv.conv('UTF-8', encoding, str)
161 return Iconv.conv('UTF-8', encoding, str)
163 rescue Iconv::Failure
162 rescue Iconv::Failure
164 # do nothing here
163 # do nothing here
165 end
164 end
166 end
165 end
167 str
166 str
168 end
167 end
169 end
168 end
@@ -1,245 +1,242
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MailHandler < ActionMailer::Base
18 class MailHandler < ActionMailer::Base
19 include ActionView::Helpers::SanitizeHelper
19 include ActionView::Helpers::SanitizeHelper
20
20
21 class UnauthorizedAction < StandardError; end
21 class UnauthorizedAction < StandardError; end
22 class MissingInformation < StandardError; end
22 class MissingInformation < StandardError; end
23
23
24 attr_reader :email, :user
24 attr_reader :email, :user
25
25
26 def self.receive(email, options={})
26 def self.receive(email, options={})
27 @@handler_options = options.dup
27 @@handler_options = options.dup
28
28
29 @@handler_options[:issue] ||= {}
29 @@handler_options[:issue] ||= {}
30
30
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
32 @@handler_options[:allow_override] ||= []
32 @@handler_options[:allow_override] ||= []
33 # Project needs to be overridable if not specified
33 # Project needs to be overridable if not specified
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
35 # Status overridable by default
35 # Status overridable by default
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
37 super email
37 super email
38 end
38 end
39
39
40 # Processes incoming emails
40 # Processes incoming emails
41 def receive(email)
41 def receive(email)
42 @email = email
42 @email = email
43 @user = User.active.find_by_mail(email.from.to_a.first.to_s.strip)
43 @user = User.active.find_by_mail(email.from.to_a.first.to_s.strip)
44 unless @user
44 unless @user
45 # Unknown user => the email is ignored
45 # Unknown user => the email is ignored
46 # TODO: ability to create the user's account
46 # TODO: ability to create the user's account
47 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
47 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
48 return false
48 return false
49 end
49 end
50 User.current = @user
50 User.current = @user
51 dispatch
51 dispatch
52 end
52 end
53
53
54 private
54 private
55
55
56 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
56 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
57 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
57 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
58 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
58 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
59
59
60 def dispatch
60 def dispatch
61 headers = [email.in_reply_to, email.references].flatten.compact
61 headers = [email.in_reply_to, email.references].flatten.compact
62 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
62 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
63 klass, object_id = $1, $2.to_i
63 klass, object_id = $1, $2.to_i
64 method_name = "receive_#{klass}_reply"
64 method_name = "receive_#{klass}_reply"
65 if self.class.private_instance_methods.include?(method_name)
65 if self.class.private_instance_methods.include?(method_name)
66 send method_name, object_id
66 send method_name, object_id
67 else
67 else
68 # ignoring it
68 # ignoring it
69 end
69 end
70 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
70 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
71 receive_issue_reply(m[1].to_i)
71 receive_issue_reply(m[1].to_i)
72 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
72 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
73 receive_message_reply(m[1].to_i)
73 receive_message_reply(m[1].to_i)
74 else
74 else
75 receive_issue
75 receive_issue
76 end
76 end
77 rescue ActiveRecord::RecordInvalid => e
77 rescue ActiveRecord::RecordInvalid => e
78 # TODO: send a email to the user
78 # TODO: send a email to the user
79 logger.error e.message if logger
79 logger.error e.message if logger
80 false
80 false
81 rescue MissingInformation => e
81 rescue MissingInformation => e
82 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
82 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
83 false
83 false
84 rescue UnauthorizedAction => e
84 rescue UnauthorizedAction => e
85 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
85 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
86 false
86 false
87 end
87 end
88
88
89 # Creates a new issue
89 # Creates a new issue
90 def receive_issue
90 def receive_issue
91 project = target_project
91 project = target_project
92 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
92 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
93 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
93 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
94 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
94 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
95 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
95 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
96
96
97 # check permission
97 # check permission
98 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
98 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
99 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
99 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
100 # check workflow
100 # check workflow
101 if status && issue.new_statuses_allowed_to(user).include?(status)
101 if status && issue.new_statuses_allowed_to(user).include?(status)
102 issue.status = status
102 issue.status = status
103 end
103 end
104 issue.subject = email.subject.chomp.toutf8
104 issue.subject = email.subject.chomp.toutf8
105 issue.description = plain_text_body
105 issue.description = plain_text_body
106 # custom fields
106 # custom fields
107 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
107 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
108 if value = get_keyword(c.name, :override => true)
108 if value = get_keyword(c.name, :override => true)
109 h[c.id] = value
109 h[c.id] = value
110 end
110 end
111 h
111 h
112 end
112 end
113 # add To and Cc as watchers before saving so the watchers can reply to Redmine
114 add_watchers(issue)
113 issue.save!
115 issue.save!
114 add_attachments(issue)
116 add_attachments(issue)
115 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
117 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
116 # add To and Cc as watchers
117 add_watchers(issue)
118 # send notification after adding watchers so that they can reply to Redmine
119 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
120 issue
118 issue
121 end
119 end
122
120
123 def target_project
121 def target_project
124 # TODO: other ways to specify project:
122 # TODO: other ways to specify project:
125 # * parse the email To field
123 # * parse the email To field
126 # * specific project (eg. Setting.mail_handler_target_project)
124 # * specific project (eg. Setting.mail_handler_target_project)
127 target = Project.find_by_identifier(get_keyword(:project))
125 target = Project.find_by_identifier(get_keyword(:project))
128 raise MissingInformation.new('Unable to determine target project') if target.nil?
126 raise MissingInformation.new('Unable to determine target project') if target.nil?
129 target
127 target
130 end
128 end
131
129
132 # Adds a note to an existing issue
130 # Adds a note to an existing issue
133 def receive_issue_reply(issue_id)
131 def receive_issue_reply(issue_id)
134 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
132 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
135
133
136 issue = Issue.find_by_id(issue_id)
134 issue = Issue.find_by_id(issue_id)
137 return unless issue
135 return unless issue
138 # check permission
136 # check permission
139 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
137 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
140 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
138 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
141
139
142 # add the note
140 # add the note
143 journal = issue.init_journal(user, plain_text_body)
141 journal = issue.init_journal(user, plain_text_body)
144 add_attachments(issue)
142 add_attachments(issue)
145 # check workflow
143 # check workflow
146 if status && issue.new_statuses_allowed_to(user).include?(status)
144 if status && issue.new_statuses_allowed_to(user).include?(status)
147 issue.status = status
145 issue.status = status
148 end
146 end
149 issue.save!
147 issue.save!
150 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
148 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
151 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
152 journal
149 journal
153 end
150 end
154
151
155 # Reply will be added to the issue
152 # Reply will be added to the issue
156 def receive_journal_reply(journal_id)
153 def receive_journal_reply(journal_id)
157 journal = Journal.find_by_id(journal_id)
154 journal = Journal.find_by_id(journal_id)
158 if journal && journal.journalized_type == 'Issue'
155 if journal && journal.journalized_type == 'Issue'
159 receive_issue_reply(journal.journalized_id)
156 receive_issue_reply(journal.journalized_id)
160 end
157 end
161 end
158 end
162
159
163 # Receives a reply to a forum message
160 # Receives a reply to a forum message
164 def receive_message_reply(message_id)
161 def receive_message_reply(message_id)
165 message = Message.find_by_id(message_id)
162 message = Message.find_by_id(message_id)
166 if message
163 if message
167 message = message.root
164 message = message.root
168 if user.allowed_to?(:add_messages, message.project) && !message.locked?
165 if user.allowed_to?(:add_messages, message.project) && !message.locked?
169 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
166 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
170 :content => plain_text_body)
167 :content => plain_text_body)
171 reply.author = user
168 reply.author = user
172 reply.board = message.board
169 reply.board = message.board
173 message.children << reply
170 message.children << reply
174 add_attachments(reply)
171 add_attachments(reply)
175 reply
172 reply
176 else
173 else
177 raise UnauthorizedAction
174 raise UnauthorizedAction
178 end
175 end
179 end
176 end
180 end
177 end
181
178
182 def add_attachments(obj)
179 def add_attachments(obj)
183 if email.has_attachments?
180 if email.has_attachments?
184 email.attachments.each do |attachment|
181 email.attachments.each do |attachment|
185 Attachment.create(:container => obj,
182 Attachment.create(:container => obj,
186 :file => attachment,
183 :file => attachment,
187 :author => user,
184 :author => user,
188 :content_type => attachment.content_type)
185 :content_type => attachment.content_type)
189 end
186 end
190 end
187 end
191 end
188 end
192
189
193 # Adds To and Cc as watchers of the given object if the sender has the
190 # Adds To and Cc as watchers of the given object if the sender has the
194 # appropriate permission
191 # appropriate permission
195 def add_watchers(obj)
192 def add_watchers(obj)
196 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
193 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
197 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
194 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
198 unless addresses.empty?
195 unless addresses.empty?
199 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
196 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
200 watchers.each {|w| obj.add_watcher(w)}
197 watchers.each {|w| obj.add_watcher(w)}
201 end
198 end
202 end
199 end
203 end
200 end
204
201
205 def get_keyword(attr, options={})
202 def get_keyword(attr, options={})
206 @keywords ||= {}
203 @keywords ||= {}
207 if @keywords.has_key?(attr)
204 if @keywords.has_key?(attr)
208 @keywords[attr]
205 @keywords[attr]
209 else
206 else
210 @keywords[attr] = begin
207 @keywords[attr] = begin
211 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
208 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
212 $1.strip
209 $1.strip
213 elsif !@@handler_options[:issue][attr].blank?
210 elsif !@@handler_options[:issue][attr].blank?
214 @@handler_options[:issue][attr]
211 @@handler_options[:issue][attr]
215 end
212 end
216 end
213 end
217 end
214 end
218 end
215 end
219
216
220 # Returns the text/plain part of the email
217 # Returns the text/plain part of the email
221 # If not found (eg. HTML-only email), returns the body with tags removed
218 # If not found (eg. HTML-only email), returns the body with tags removed
222 def plain_text_body
219 def plain_text_body
223 return @plain_text_body unless @plain_text_body.nil?
220 return @plain_text_body unless @plain_text_body.nil?
224 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
221 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
225 if parts.empty?
222 if parts.empty?
226 parts << @email
223 parts << @email
227 end
224 end
228 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
225 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
229 if plain_text_part.nil?
226 if plain_text_part.nil?
230 # no text/plain part found, assuming html-only email
227 # no text/plain part found, assuming html-only email
231 # strip html tags and remove doctype directive
228 # strip html tags and remove doctype directive
232 @plain_text_body = strip_tags(@email.body.to_s)
229 @plain_text_body = strip_tags(@email.body.to_s)
233 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
230 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
234 else
231 else
235 @plain_text_body = plain_text_part.body.to_s
232 @plain_text_body = plain_text_part.body.to_s
236 end
233 end
237 @plain_text_body.strip!
234 @plain_text_body.strip!
238 @plain_text_body
235 @plain_text_body
239 end
236 end
240
237
241
238
242 def self.full_sanitizer
239 def self.full_sanitizer
243 @full_sanitizer ||= HTML::FullSanitizer.new
240 @full_sanitizer ||= HTML::FullSanitizer.new
244 end
241 end
245 end
242 end
@@ -1,52 +1,52
1 # Be sure to restart your web server when you modify this file.
1 # Be sure to restart your web server when you modify this file.
2
2
3 # Uncomment below to force Rails into production mode when
3 # Uncomment below to force Rails into production mode when
4 # you don't control web/app server and can't set it the proper way
4 # you don't control web/app server and can't set it the proper way
5 # ENV['RAILS_ENV'] ||= 'production'
5 # ENV['RAILS_ENV'] ||= 'production'
6
6
7 # Specifies gem version of Rails to use when vendor/rails is not present
7 # Specifies gem version of Rails to use when vendor/rails is not present
8 RAILS_GEM_VERSION = '2.2.2' unless defined? RAILS_GEM_VERSION
8 RAILS_GEM_VERSION = '2.2.2' unless defined? RAILS_GEM_VERSION
9
9
10 # Bootstrap the Rails environment, frameworks, and default configuration
10 # Bootstrap the Rails environment, frameworks, and default configuration
11 require File.join(File.dirname(__FILE__), 'boot')
11 require File.join(File.dirname(__FILE__), 'boot')
12
12
13 # Load Engine plugin if available
13 # Load Engine plugin if available
14 begin
14 begin
15 require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
15 require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
16 rescue LoadError
16 rescue LoadError
17 # Not available
17 # Not available
18 end
18 end
19
19
20 Rails::Initializer.run do |config|
20 Rails::Initializer.run do |config|
21 # Settings in config/environments/* take precedence those specified here
21 # Settings in config/environments/* take precedence those specified here
22
22
23 # Skip frameworks you're not going to use
23 # Skip frameworks you're not going to use
24 # config.frameworks -= [ :action_web_service, :action_mailer ]
24 # config.frameworks -= [ :action_web_service, :action_mailer ]
25
25
26 # Add additional load paths for sweepers
26 # Add additional load paths for sweepers
27 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
27 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
28
28
29 # Force all environments to use the same logger level
29 # Force all environments to use the same logger level
30 # (by default production uses :info, the others :debug)
30 # (by default production uses :info, the others :debug)
31 # config.log_level = :debug
31 # config.log_level = :debug
32
32
33 # Enable page/fragment caching by setting a file-based store
33 # Enable page/fragment caching by setting a file-based store
34 # (remember to create the caching directory and make it readable to the application)
34 # (remember to create the caching directory and make it readable to the application)
35 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
35 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
36
36
37 # Activate observers that should always be running
37 # Activate observers that should always be running
38 # config.active_record.observers = :cacher, :garbage_collector
38 # config.active_record.observers = :cacher, :garbage_collector
39 config.active_record.observers = :message_observer
39 config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer
40
40
41 # Make Active Record use UTC-base instead of local time
41 # Make Active Record use UTC-base instead of local time
42 # config.active_record.default_timezone = :utc
42 # config.active_record.default_timezone = :utc
43
43
44 # Use Active Record's schema dumper instead of SQL when creating the test database
44 # Use Active Record's schema dumper instead of SQL when creating the test database
45 # (enables use of different database adapters for development and test environments)
45 # (enables use of different database adapters for development and test environments)
46 # config.active_record.schema_format = :ruby
46 # config.active_record.schema_format = :ruby
47
47
48 # Deliveries are disabled by default. Do NOT modify this section.
48 # Deliveries are disabled by default. Do NOT modify this section.
49 # Define your email configuration in email.yml instead.
49 # Define your email configuration in email.yml instead.
50 # It will automatically turn deliveries on
50 # It will automatically turn deliveries on
51 config.action_mailer.perform_deliveries = false
51 config.action_mailer.perform_deliveries = false
52 end
52 end
@@ -1,118 +1,121
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'documents_controller'
19 require 'documents_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class DocumentsController; def rescue_action(e) raise e end; end
22 class DocumentsController; def rescue_action(e) raise e end; end
23
23
24 class DocumentsControllerTest < Test::Unit::TestCase
24 class DocumentsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :enabled_modules, :documents, :enumerations
25 fixtures :projects, :users, :roles, :members, :enabled_modules, :documents, :enumerations
26
26
27 def setup
27 def setup
28 @controller = DocumentsController.new
28 @controller = DocumentsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 end
32 end
33
33
34 def test_index_routing
34 def test_index_routing
35 assert_routing(
35 assert_routing(
36 {:method => :get, :path => '/projects/567/documents'},
36 {:method => :get, :path => '/projects/567/documents'},
37 :controller => 'documents', :action => 'index', :project_id => '567'
37 :controller => 'documents', :action => 'index', :project_id => '567'
38 )
38 )
39 end
39 end
40
40
41 def test_index
41 def test_index
42 # Sets a default category
42 # Sets a default category
43 e = Enumeration.find_by_name('Technical documentation')
43 e = Enumeration.find_by_name('Technical documentation')
44 e.update_attributes(:is_default => true)
44 e.update_attributes(:is_default => true)
45
45
46 get :index, :project_id => 'ecookbook'
46 get :index, :project_id => 'ecookbook'
47 assert_response :success
47 assert_response :success
48 assert_template 'index'
48 assert_template 'index'
49 assert_not_nil assigns(:grouped)
49 assert_not_nil assigns(:grouped)
50
50
51 # Default category selected in the new document form
51 # Default category selected in the new document form
52 assert_tag :select, :attributes => {:name => 'document[category_id]'},
52 assert_tag :select, :attributes => {:name => 'document[category_id]'},
53 :child => {:tag => 'option', :attributes => {:selected => 'selected'},
53 :child => {:tag => 'option', :attributes => {:selected => 'selected'},
54 :content => 'Technical documentation'}
54 :content => 'Technical documentation'}
55 end
55 end
56
56
57 def test_new_routing
57 def test_new_routing
58 assert_routing(
58 assert_routing(
59 {:method => :get, :path => '/projects/567/documents/new'},
59 {:method => :get, :path => '/projects/567/documents/new'},
60 :controller => 'documents', :action => 'new', :project_id => '567'
60 :controller => 'documents', :action => 'new', :project_id => '567'
61 )
61 )
62 assert_recognizes(
62 assert_recognizes(
63 {:controller => 'documents', :action => 'new', :project_id => '567'},
63 {:controller => 'documents', :action => 'new', :project_id => '567'},
64 {:method => :post, :path => '/projects/567/documents'}
64 {:method => :post, :path => '/projects/567/documents'}
65 )
65 )
66 end
66 end
67
67
68 def test_new_with_one_attachment
68 def test_new_with_one_attachment
69 ActionMailer::Base.deliveries.clear
70 Setting.notified_events << 'document_added'
69 @request.session[:user_id] = 2
71 @request.session[:user_id] = 2
70 set_tmp_attachments_directory
72 set_tmp_attachments_directory
71
73
72 post :new, :project_id => 'ecookbook',
74 post :new, :project_id => 'ecookbook',
73 :document => { :title => 'DocumentsControllerTest#test_post_new',
75 :document => { :title => 'DocumentsControllerTest#test_post_new',
74 :description => 'This is a new document',
76 :description => 'This is a new document',
75 :category_id => 2},
77 :category_id => 2},
76 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
78 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
77
79
78 assert_redirected_to 'projects/ecookbook/documents'
80 assert_redirected_to 'projects/ecookbook/documents'
79
81
80 document = Document.find_by_title('DocumentsControllerTest#test_post_new')
82 document = Document.find_by_title('DocumentsControllerTest#test_post_new')
81 assert_not_nil document
83 assert_not_nil document
82 assert_equal Enumeration.find(2), document.category
84 assert_equal Enumeration.find(2), document.category
83 assert_equal 1, document.attachments.size
85 assert_equal 1, document.attachments.size
84 assert_equal 'testfile.txt', document.attachments.first.filename
86 assert_equal 'testfile.txt', document.attachments.first.filename
87 assert_equal 1, ActionMailer::Base.deliveries.size
85 end
88 end
86
89
87 def test_edit_routing
90 def test_edit_routing
88 assert_routing(
91 assert_routing(
89 {:method => :get, :path => '/documents/22/edit'},
92 {:method => :get, :path => '/documents/22/edit'},
90 :controller => 'documents', :action => 'edit', :id => '22'
93 :controller => 'documents', :action => 'edit', :id => '22'
91 )
94 )
92 assert_recognizes(#TODO: should be using PUT on document URI
95 assert_recognizes(#TODO: should be using PUT on document URI
93 {:controller => 'documents', :action => 'edit', :id => '567'},
96 {:controller => 'documents', :action => 'edit', :id => '567'},
94 {:method => :post, :path => '/documents/567/edit'}
97 {:method => :post, :path => '/documents/567/edit'}
95 )
98 )
96 end
99 end
97
100
98 def test_show_routing
101 def test_show_routing
99 assert_routing(
102 assert_routing(
100 {:method => :get, :path => '/documents/22'},
103 {:method => :get, :path => '/documents/22'},
101 :controller => 'documents', :action => 'show', :id => '22'
104 :controller => 'documents', :action => 'show', :id => '22'
102 )
105 )
103 end
106 end
104
107
105 def test_destroy_routing
108 def test_destroy_routing
106 assert_recognizes(#TODO: should be using DELETE on document URI
109 assert_recognizes(#TODO: should be using DELETE on document URI
107 {:controller => 'documents', :action => 'destroy', :id => '567'},
110 {:controller => 'documents', :action => 'destroy', :id => '567'},
108 {:method => :post, :path => '/documents/567/destroy'}
111 {:method => :post, :path => '/documents/567/destroy'}
109 )
112 )
110 end
113 end
111
114
112 def test_destroy
115 def test_destroy
113 @request.session[:user_id] = 2
116 @request.session[:user_id] = 2
114 post :destroy, :id => 1
117 post :destroy, :id => 1
115 assert_redirected_to 'projects/ecookbook/documents'
118 assert_redirected_to 'projects/ecookbook/documents'
116 assert_nil Document.find_by_id(1)
119 assert_nil Document.find_by_id(1)
117 end
120 end
118 end
121 end
@@ -1,993 +1,1038
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :versions,
31 :versions,
32 :trackers,
32 :trackers,
33 :projects_trackers,
33 :projects_trackers,
34 :issue_categories,
34 :issue_categories,
35 :enabled_modules,
35 :enabled_modules,
36 :enumerations,
36 :enumerations,
37 :attachments,
37 :attachments,
38 :workflows,
38 :workflows,
39 :custom_fields,
39 :custom_fields,
40 :custom_values,
40 :custom_values,
41 :custom_fields_trackers,
41 :custom_fields_trackers,
42 :time_entries,
42 :time_entries,
43 :journals,
43 :journals,
44 :journal_details
44 :journal_details
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index_routing
53 def test_index_routing
54 assert_routing(
54 assert_routing(
55 {:method => :get, :path => '/issues'},
55 {:method => :get, :path => '/issues'},
56 :controller => 'issues', :action => 'index'
56 :controller => 'issues', :action => 'index'
57 )
57 )
58 end
58 end
59
59
60 def test_index
60 def test_index
61 Setting.default_language = 'en'
61 Setting.default_language = 'en'
62
62
63 get :index
63 get :index
64 assert_response :success
64 assert_response :success
65 assert_template 'index.rhtml'
65 assert_template 'index.rhtml'
66 assert_not_nil assigns(:issues)
66 assert_not_nil assigns(:issues)
67 assert_nil assigns(:project)
67 assert_nil assigns(:project)
68 assert_tag :tag => 'a', :content => /Can't print recipes/
68 assert_tag :tag => 'a', :content => /Can't print recipes/
69 assert_tag :tag => 'a', :content => /Subproject issue/
69 assert_tag :tag => 'a', :content => /Subproject issue/
70 # private projects hidden
70 # private projects hidden
71 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
71 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
72 assert_no_tag :tag => 'a', :content => /Issue on project 2/
72 assert_no_tag :tag => 'a', :content => /Issue on project 2/
73 # project column
73 # project column
74 assert_tag :tag => 'th', :content => /Project/
74 assert_tag :tag => 'th', :content => /Project/
75 end
75 end
76
76
77 def test_index_should_not_list_issues_when_module_disabled
77 def test_index_should_not_list_issues_when_module_disabled
78 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
78 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
79 get :index
79 get :index
80 assert_response :success
80 assert_response :success
81 assert_template 'index.rhtml'
81 assert_template 'index.rhtml'
82 assert_not_nil assigns(:issues)
82 assert_not_nil assigns(:issues)
83 assert_nil assigns(:project)
83 assert_nil assigns(:project)
84 assert_no_tag :tag => 'a', :content => /Can't print recipes/
84 assert_no_tag :tag => 'a', :content => /Can't print recipes/
85 assert_tag :tag => 'a', :content => /Subproject issue/
85 assert_tag :tag => 'a', :content => /Subproject issue/
86 end
86 end
87
87
88 def test_index_with_project_routing
88 def test_index_with_project_routing
89 assert_routing(
89 assert_routing(
90 {:method => :get, :path => '/projects/23/issues'},
90 {:method => :get, :path => '/projects/23/issues'},
91 :controller => 'issues', :action => 'index', :project_id => '23'
91 :controller => 'issues', :action => 'index', :project_id => '23'
92 )
92 )
93 end
93 end
94
94
95 def test_index_should_not_list_issues_when_module_disabled
95 def test_index_should_not_list_issues_when_module_disabled
96 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
96 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
97 get :index
97 get :index
98 assert_response :success
98 assert_response :success
99 assert_template 'index.rhtml'
99 assert_template 'index.rhtml'
100 assert_not_nil assigns(:issues)
100 assert_not_nil assigns(:issues)
101 assert_nil assigns(:project)
101 assert_nil assigns(:project)
102 assert_no_tag :tag => 'a', :content => /Can't print recipes/
102 assert_no_tag :tag => 'a', :content => /Can't print recipes/
103 assert_tag :tag => 'a', :content => /Subproject issue/
103 assert_tag :tag => 'a', :content => /Subproject issue/
104 end
104 end
105
105
106 def test_index_with_project_routing
106 def test_index_with_project_routing
107 assert_routing(
107 assert_routing(
108 {:method => :get, :path => 'projects/23/issues'},
108 {:method => :get, :path => 'projects/23/issues'},
109 :controller => 'issues', :action => 'index', :project_id => '23'
109 :controller => 'issues', :action => 'index', :project_id => '23'
110 )
110 )
111 end
111 end
112
112
113 def test_index_with_project
113 def test_index_with_project
114 Setting.display_subprojects_issues = 0
114 Setting.display_subprojects_issues = 0
115 get :index, :project_id => 1
115 get :index, :project_id => 1
116 assert_response :success
116 assert_response :success
117 assert_template 'index.rhtml'
117 assert_template 'index.rhtml'
118 assert_not_nil assigns(:issues)
118 assert_not_nil assigns(:issues)
119 assert_tag :tag => 'a', :content => /Can't print recipes/
119 assert_tag :tag => 'a', :content => /Can't print recipes/
120 assert_no_tag :tag => 'a', :content => /Subproject issue/
120 assert_no_tag :tag => 'a', :content => /Subproject issue/
121 end
121 end
122
122
123 def test_index_with_project_and_subprojects
123 def test_index_with_project_and_subprojects
124 Setting.display_subprojects_issues = 1
124 Setting.display_subprojects_issues = 1
125 get :index, :project_id => 1
125 get :index, :project_id => 1
126 assert_response :success
126 assert_response :success
127 assert_template 'index.rhtml'
127 assert_template 'index.rhtml'
128 assert_not_nil assigns(:issues)
128 assert_not_nil assigns(:issues)
129 assert_tag :tag => 'a', :content => /Can't print recipes/
129 assert_tag :tag => 'a', :content => /Can't print recipes/
130 assert_tag :tag => 'a', :content => /Subproject issue/
130 assert_tag :tag => 'a', :content => /Subproject issue/
131 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
131 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
132 end
132 end
133
133
134 def test_index_with_project_and_subprojects_should_show_private_subprojects
134 def test_index_with_project_and_subprojects_should_show_private_subprojects
135 @request.session[:user_id] = 2
135 @request.session[:user_id] = 2
136 Setting.display_subprojects_issues = 1
136 Setting.display_subprojects_issues = 1
137 get :index, :project_id => 1
137 get :index, :project_id => 1
138 assert_response :success
138 assert_response :success
139 assert_template 'index.rhtml'
139 assert_template 'index.rhtml'
140 assert_not_nil assigns(:issues)
140 assert_not_nil assigns(:issues)
141 assert_tag :tag => 'a', :content => /Can't print recipes/
141 assert_tag :tag => 'a', :content => /Can't print recipes/
142 assert_tag :tag => 'a', :content => /Subproject issue/
142 assert_tag :tag => 'a', :content => /Subproject issue/
143 assert_tag :tag => 'a', :content => /Issue of a private subproject/
143 assert_tag :tag => 'a', :content => /Issue of a private subproject/
144 end
144 end
145
145
146 def test_index_with_project_routing_formatted
146 def test_index_with_project_routing_formatted
147 assert_routing(
147 assert_routing(
148 {:method => :get, :path => 'projects/23/issues.pdf'},
148 {:method => :get, :path => 'projects/23/issues.pdf'},
149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
150 )
150 )
151 assert_routing(
151 assert_routing(
152 {:method => :get, :path => 'projects/23/issues.atom'},
152 {:method => :get, :path => 'projects/23/issues.atom'},
153 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
153 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
154 )
154 )
155 end
155 end
156
156
157 def test_index_with_project_and_filter
157 def test_index_with_project_and_filter
158 get :index, :project_id => 1, :set_filter => 1
158 get :index, :project_id => 1, :set_filter => 1
159 assert_response :success
159 assert_response :success
160 assert_template 'index.rhtml'
160 assert_template 'index.rhtml'
161 assert_not_nil assigns(:issues)
161 assert_not_nil assigns(:issues)
162 end
162 end
163
163
164 def test_index_csv_with_project
164 def test_index_csv_with_project
165 get :index, :format => 'csv'
165 get :index, :format => 'csv'
166 assert_response :success
166 assert_response :success
167 assert_not_nil assigns(:issues)
167 assert_not_nil assigns(:issues)
168 assert_equal 'text/csv', @response.content_type
168 assert_equal 'text/csv', @response.content_type
169
169
170 get :index, :project_id => 1, :format => 'csv'
170 get :index, :project_id => 1, :format => 'csv'
171 assert_response :success
171 assert_response :success
172 assert_not_nil assigns(:issues)
172 assert_not_nil assigns(:issues)
173 assert_equal 'text/csv', @response.content_type
173 assert_equal 'text/csv', @response.content_type
174 end
174 end
175
175
176 def test_index_formatted
176 def test_index_formatted
177 assert_routing(
177 assert_routing(
178 {:method => :get, :path => 'issues.pdf'},
178 {:method => :get, :path => 'issues.pdf'},
179 :controller => 'issues', :action => 'index', :format => 'pdf'
179 :controller => 'issues', :action => 'index', :format => 'pdf'
180 )
180 )
181 assert_routing(
181 assert_routing(
182 {:method => :get, :path => 'issues.atom'},
182 {:method => :get, :path => 'issues.atom'},
183 :controller => 'issues', :action => 'index', :format => 'atom'
183 :controller => 'issues', :action => 'index', :format => 'atom'
184 )
184 )
185 end
185 end
186
186
187 def test_index_pdf
187 def test_index_pdf
188 get :index, :format => 'pdf'
188 get :index, :format => 'pdf'
189 assert_response :success
189 assert_response :success
190 assert_not_nil assigns(:issues)
190 assert_not_nil assigns(:issues)
191 assert_equal 'application/pdf', @response.content_type
191 assert_equal 'application/pdf', @response.content_type
192
192
193 get :index, :project_id => 1, :format => 'pdf'
193 get :index, :project_id => 1, :format => 'pdf'
194 assert_response :success
194 assert_response :success
195 assert_not_nil assigns(:issues)
195 assert_not_nil assigns(:issues)
196 assert_equal 'application/pdf', @response.content_type
196 assert_equal 'application/pdf', @response.content_type
197 end
197 end
198
198
199 def test_index_sort
199 def test_index_sort
200 get :index, :sort => 'tracker,id:desc'
200 get :index, :sort => 'tracker,id:desc'
201 assert_response :success
201 assert_response :success
202
202
203 sort_params = @request.session['issues_index_sort']
203 sort_params = @request.session['issues_index_sort']
204 assert sort_params.is_a?(String)
204 assert sort_params.is_a?(String)
205 assert_equal 'tracker,id:desc', sort_params
205 assert_equal 'tracker,id:desc', sort_params
206
206
207 issues = assigns(:issues)
207 issues = assigns(:issues)
208 assert_not_nil issues
208 assert_not_nil issues
209 assert !issues.empty?
209 assert !issues.empty?
210 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
210 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
211 end
211 end
212
212
213 def test_gantt
213 def test_gantt
214 get :gantt, :project_id => 1
214 get :gantt, :project_id => 1
215 assert_response :success
215 assert_response :success
216 assert_template 'gantt.rhtml'
216 assert_template 'gantt.rhtml'
217 assert_not_nil assigns(:gantt)
217 assert_not_nil assigns(:gantt)
218 events = assigns(:gantt).events
218 events = assigns(:gantt).events
219 assert_not_nil events
219 assert_not_nil events
220 # Issue with start and due dates
220 # Issue with start and due dates
221 i = Issue.find(1)
221 i = Issue.find(1)
222 assert_not_nil i.due_date
222 assert_not_nil i.due_date
223 assert events.include?(Issue.find(1))
223 assert events.include?(Issue.find(1))
224 # Issue with without due date but targeted to a version with date
224 # Issue with without due date but targeted to a version with date
225 i = Issue.find(2)
225 i = Issue.find(2)
226 assert_nil i.due_date
226 assert_nil i.due_date
227 assert events.include?(i)
227 assert events.include?(i)
228 end
228 end
229
229
230 def test_cross_project_gantt
230 def test_cross_project_gantt
231 get :gantt
231 get :gantt
232 assert_response :success
232 assert_response :success
233 assert_template 'gantt.rhtml'
233 assert_template 'gantt.rhtml'
234 assert_not_nil assigns(:gantt)
234 assert_not_nil assigns(:gantt)
235 events = assigns(:gantt).events
235 events = assigns(:gantt).events
236 assert_not_nil events
236 assert_not_nil events
237 end
237 end
238
238
239 def test_gantt_export_to_pdf
239 def test_gantt_export_to_pdf
240 get :gantt, :project_id => 1, :format => 'pdf'
240 get :gantt, :project_id => 1, :format => 'pdf'
241 assert_response :success
241 assert_response :success
242 assert_equal 'application/pdf', @response.content_type
242 assert_equal 'application/pdf', @response.content_type
243 assert @response.body.starts_with?('%PDF')
243 assert @response.body.starts_with?('%PDF')
244 assert_not_nil assigns(:gantt)
244 assert_not_nil assigns(:gantt)
245 end
245 end
246
246
247 def test_cross_project_gantt_export_to_pdf
247 def test_cross_project_gantt_export_to_pdf
248 get :gantt, :format => 'pdf'
248 get :gantt, :format => 'pdf'
249 assert_response :success
249 assert_response :success
250 assert_equal 'application/pdf', @response.content_type
250 assert_equal 'application/pdf', @response.content_type
251 assert @response.body.starts_with?('%PDF')
251 assert @response.body.starts_with?('%PDF')
252 assert_not_nil assigns(:gantt)
252 assert_not_nil assigns(:gantt)
253 end
253 end
254
254
255 if Object.const_defined?(:Magick)
255 if Object.const_defined?(:Magick)
256 def test_gantt_image
256 def test_gantt_image
257 get :gantt, :project_id => 1, :format => 'png'
257 get :gantt, :project_id => 1, :format => 'png'
258 assert_response :success
258 assert_response :success
259 assert_equal 'image/png', @response.content_type
259 assert_equal 'image/png', @response.content_type
260 end
260 end
261 else
261 else
262 puts "RMagick not installed. Skipping tests !!!"
262 puts "RMagick not installed. Skipping tests !!!"
263 end
263 end
264
264
265 def test_calendar
265 def test_calendar
266 get :calendar, :project_id => 1
266 get :calendar, :project_id => 1
267 assert_response :success
267 assert_response :success
268 assert_template 'calendar'
268 assert_template 'calendar'
269 assert_not_nil assigns(:calendar)
269 assert_not_nil assigns(:calendar)
270 end
270 end
271
271
272 def test_cross_project_calendar
272 def test_cross_project_calendar
273 get :calendar
273 get :calendar
274 assert_response :success
274 assert_response :success
275 assert_template 'calendar'
275 assert_template 'calendar'
276 assert_not_nil assigns(:calendar)
276 assert_not_nil assigns(:calendar)
277 end
277 end
278
278
279 def test_changes
279 def test_changes
280 get :changes, :project_id => 1
280 get :changes, :project_id => 1
281 assert_response :success
281 assert_response :success
282 assert_not_nil assigns(:journals)
282 assert_not_nil assigns(:journals)
283 assert_equal 'application/atom+xml', @response.content_type
283 assert_equal 'application/atom+xml', @response.content_type
284 end
284 end
285
285
286 def test_show_routing
286 def test_show_routing
287 assert_routing(
287 assert_routing(
288 {:method => :get, :path => '/issues/64'},
288 {:method => :get, :path => '/issues/64'},
289 :controller => 'issues', :action => 'show', :id => '64'
289 :controller => 'issues', :action => 'show', :id => '64'
290 )
290 )
291 end
291 end
292
292
293 def test_show_routing_formatted
293 def test_show_routing_formatted
294 assert_routing(
294 assert_routing(
295 {:method => :get, :path => '/issues/2332.pdf'},
295 {:method => :get, :path => '/issues/2332.pdf'},
296 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
296 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
297 )
297 )
298 assert_routing(
298 assert_routing(
299 {:method => :get, :path => '/issues/23123.atom'},
299 {:method => :get, :path => '/issues/23123.atom'},
300 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
300 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
301 )
301 )
302 end
302 end
303
303
304 def test_show_by_anonymous
304 def test_show_by_anonymous
305 get :show, :id => 1
305 get :show, :id => 1
306 assert_response :success
306 assert_response :success
307 assert_template 'show.rhtml'
307 assert_template 'show.rhtml'
308 assert_not_nil assigns(:issue)
308 assert_not_nil assigns(:issue)
309 assert_equal Issue.find(1), assigns(:issue)
309 assert_equal Issue.find(1), assigns(:issue)
310
310
311 # anonymous role is allowed to add a note
311 # anonymous role is allowed to add a note
312 assert_tag :tag => 'form',
312 assert_tag :tag => 'form',
313 :descendant => { :tag => 'fieldset',
313 :descendant => { :tag => 'fieldset',
314 :child => { :tag => 'legend',
314 :child => { :tag => 'legend',
315 :content => /Notes/ } }
315 :content => /Notes/ } }
316 end
316 end
317
317
318 def test_show_by_manager
318 def test_show_by_manager
319 @request.session[:user_id] = 2
319 @request.session[:user_id] = 2
320 get :show, :id => 1
320 get :show, :id => 1
321 assert_response :success
321 assert_response :success
322
322
323 assert_tag :tag => 'form',
323 assert_tag :tag => 'form',
324 :descendant => { :tag => 'fieldset',
324 :descendant => { :tag => 'fieldset',
325 :child => { :tag => 'legend',
325 :child => { :tag => 'legend',
326 :content => /Change properties/ } },
326 :content => /Change properties/ } },
327 :descendant => { :tag => 'fieldset',
327 :descendant => { :tag => 'fieldset',
328 :child => { :tag => 'legend',
328 :child => { :tag => 'legend',
329 :content => /Log time/ } },
329 :content => /Log time/ } },
330 :descendant => { :tag => 'fieldset',
330 :descendant => { :tag => 'fieldset',
331 :child => { :tag => 'legend',
331 :child => { :tag => 'legend',
332 :content => /Notes/ } }
332 :content => /Notes/ } }
333 end
333 end
334
334
335 def test_show_should_not_disclose_relations_to_invisible_issues
335 def test_show_should_not_disclose_relations_to_invisible_issues
336 Setting.cross_project_issue_relations = '1'
336 Setting.cross_project_issue_relations = '1'
337 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
337 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
338 # Relation to a private project issue
338 # Relation to a private project issue
339 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
339 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
340
340
341 get :show, :id => 1
341 get :show, :id => 1
342 assert_response :success
342 assert_response :success
343
343
344 assert_tag :div, :attributes => { :id => 'relations' },
344 assert_tag :div, :attributes => { :id => 'relations' },
345 :descendant => { :tag => 'a', :content => /#2$/ }
345 :descendant => { :tag => 'a', :content => /#2$/ }
346 assert_no_tag :div, :attributes => { :id => 'relations' },
346 assert_no_tag :div, :attributes => { :id => 'relations' },
347 :descendant => { :tag => 'a', :content => /#4$/ }
347 :descendant => { :tag => 'a', :content => /#4$/ }
348 end
348 end
349
349
350 def test_new_routing
350 def test_new_routing
351 assert_routing(
351 assert_routing(
352 {:method => :get, :path => '/projects/1/issues/new'},
352 {:method => :get, :path => '/projects/1/issues/new'},
353 :controller => 'issues', :action => 'new', :project_id => '1'
353 :controller => 'issues', :action => 'new', :project_id => '1'
354 )
354 )
355 assert_recognizes(
355 assert_recognizes(
356 {:controller => 'issues', :action => 'new', :project_id => '1'},
356 {:controller => 'issues', :action => 'new', :project_id => '1'},
357 {:method => :post, :path => '/projects/1/issues'}
357 {:method => :post, :path => '/projects/1/issues'}
358 )
358 )
359 end
359 end
360
360
361 def test_show_export_to_pdf
361 def test_show_export_to_pdf
362 get :show, :id => 3, :format => 'pdf'
362 get :show, :id => 3, :format => 'pdf'
363 assert_response :success
363 assert_response :success
364 assert_equal 'application/pdf', @response.content_type
364 assert_equal 'application/pdf', @response.content_type
365 assert @response.body.starts_with?('%PDF')
365 assert @response.body.starts_with?('%PDF')
366 assert_not_nil assigns(:issue)
366 assert_not_nil assigns(:issue)
367 end
367 end
368
368
369 def test_get_new
369 def test_get_new
370 @request.session[:user_id] = 2
370 @request.session[:user_id] = 2
371 get :new, :project_id => 1, :tracker_id => 1
371 get :new, :project_id => 1, :tracker_id => 1
372 assert_response :success
372 assert_response :success
373 assert_template 'new'
373 assert_template 'new'
374
374
375 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
375 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
376 :value => 'Default string' }
376 :value => 'Default string' }
377 end
377 end
378
378
379 def test_get_new_without_tracker_id
379 def test_get_new_without_tracker_id
380 @request.session[:user_id] = 2
380 @request.session[:user_id] = 2
381 get :new, :project_id => 1
381 get :new, :project_id => 1
382 assert_response :success
382 assert_response :success
383 assert_template 'new'
383 assert_template 'new'
384
384
385 issue = assigns(:issue)
385 issue = assigns(:issue)
386 assert_not_nil issue
386 assert_not_nil issue
387 assert_equal Project.find(1).trackers.first, issue.tracker
387 assert_equal Project.find(1).trackers.first, issue.tracker
388 end
388 end
389
389
390 def test_get_new_with_no_default_status_should_display_an_error
390 def test_get_new_with_no_default_status_should_display_an_error
391 @request.session[:user_id] = 2
391 @request.session[:user_id] = 2
392 IssueStatus.delete_all
392 IssueStatus.delete_all
393
393
394 get :new, :project_id => 1
394 get :new, :project_id => 1
395 assert_response 500
395 assert_response 500
396 assert_not_nil flash[:error]
396 assert_not_nil flash[:error]
397 assert_tag :tag => 'div', :attributes => { :class => /error/ },
397 assert_tag :tag => 'div', :attributes => { :class => /error/ },
398 :content => /No default issue/
398 :content => /No default issue/
399 end
399 end
400
400
401 def test_get_new_with_no_tracker_should_display_an_error
401 def test_get_new_with_no_tracker_should_display_an_error
402 @request.session[:user_id] = 2
402 @request.session[:user_id] = 2
403 Tracker.delete_all
403 Tracker.delete_all
404
404
405 get :new, :project_id => 1
405 get :new, :project_id => 1
406 assert_response 500
406 assert_response 500
407 assert_not_nil flash[:error]
407 assert_not_nil flash[:error]
408 assert_tag :tag => 'div', :attributes => { :class => /error/ },
408 assert_tag :tag => 'div', :attributes => { :class => /error/ },
409 :content => /No tracker/
409 :content => /No tracker/
410 end
410 end
411
411
412 def test_update_new_form
412 def test_update_new_form
413 @request.session[:user_id] = 2
413 @request.session[:user_id] = 2
414 xhr :post, :new, :project_id => 1,
414 xhr :post, :new, :project_id => 1,
415 :issue => {:tracker_id => 2,
415 :issue => {:tracker_id => 2,
416 :subject => 'This is the test_new issue',
416 :subject => 'This is the test_new issue',
417 :description => 'This is the description',
417 :description => 'This is the description',
418 :priority_id => 5}
418 :priority_id => 5}
419 assert_response :success
419 assert_response :success
420 assert_template 'new'
420 assert_template 'new'
421 end
421 end
422
422
423 def test_post_new
423 def test_post_new
424 @request.session[:user_id] = 2
424 @request.session[:user_id] = 2
425 post :new, :project_id => 1,
425 post :new, :project_id => 1,
426 :issue => {:tracker_id => 3,
426 :issue => {:tracker_id => 3,
427 :subject => 'This is the test_new issue',
427 :subject => 'This is the test_new issue',
428 :description => 'This is the description',
428 :description => 'This is the description',
429 :priority_id => 5,
429 :priority_id => 5,
430 :estimated_hours => '',
430 :estimated_hours => '',
431 :custom_field_values => {'2' => 'Value for field 2'}}
431 :custom_field_values => {'2' => 'Value for field 2'}}
432 assert_redirected_to :action => 'show'
432 assert_redirected_to :action => 'show'
433
433
434 issue = Issue.find_by_subject('This is the test_new issue')
434 issue = Issue.find_by_subject('This is the test_new issue')
435 assert_not_nil issue
435 assert_not_nil issue
436 assert_equal 2, issue.author_id
436 assert_equal 2, issue.author_id
437 assert_equal 3, issue.tracker_id
437 assert_equal 3, issue.tracker_id
438 assert_nil issue.estimated_hours
438 assert_nil issue.estimated_hours
439 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
439 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
440 assert_not_nil v
440 assert_not_nil v
441 assert_equal 'Value for field 2', v.value
441 assert_equal 'Value for field 2', v.value
442 end
442 end
443
443
444 def test_post_new_and_continue
444 def test_post_new_and_continue
445 @request.session[:user_id] = 2
445 @request.session[:user_id] = 2
446 post :new, :project_id => 1,
446 post :new, :project_id => 1,
447 :issue => {:tracker_id => 3,
447 :issue => {:tracker_id => 3,
448 :subject => 'This is first issue',
448 :subject => 'This is first issue',
449 :priority_id => 5},
449 :priority_id => 5},
450 :continue => ''
450 :continue => ''
451 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
451 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
452 end
452 end
453
453
454 def test_post_new_without_custom_fields_param
454 def test_post_new_without_custom_fields_param
455 @request.session[:user_id] = 2
455 @request.session[:user_id] = 2
456 post :new, :project_id => 1,
456 post :new, :project_id => 1,
457 :issue => {:tracker_id => 1,
457 :issue => {:tracker_id => 1,
458 :subject => 'This is the test_new issue',
458 :subject => 'This is the test_new issue',
459 :description => 'This is the description',
459 :description => 'This is the description',
460 :priority_id => 5}
460 :priority_id => 5}
461 assert_redirected_to :action => 'show'
461 assert_redirected_to :action => 'show'
462 end
462 end
463
463
464 def test_post_new_with_required_custom_field_and_without_custom_fields_param
464 def test_post_new_with_required_custom_field_and_without_custom_fields_param
465 field = IssueCustomField.find_by_name('Database')
465 field = IssueCustomField.find_by_name('Database')
466 field.update_attribute(:is_required, true)
466 field.update_attribute(:is_required, true)
467
467
468 @request.session[:user_id] = 2
468 @request.session[:user_id] = 2
469 post :new, :project_id => 1,
469 post :new, :project_id => 1,
470 :issue => {:tracker_id => 1,
470 :issue => {:tracker_id => 1,
471 :subject => 'This is the test_new issue',
471 :subject => 'This is the test_new issue',
472 :description => 'This is the description',
472 :description => 'This is the description',
473 :priority_id => 5}
473 :priority_id => 5}
474 assert_response :success
474 assert_response :success
475 assert_template 'new'
475 assert_template 'new'
476 issue = assigns(:issue)
476 issue = assigns(:issue)
477 assert_not_nil issue
477 assert_not_nil issue
478 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
478 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
479 end
479 end
480
480
481 def test_post_new_with_watchers
481 def test_post_new_with_watchers
482 @request.session[:user_id] = 2
482 @request.session[:user_id] = 2
483 ActionMailer::Base.deliveries.clear
483 ActionMailer::Base.deliveries.clear
484
484
485 assert_difference 'Watcher.count', 2 do
485 assert_difference 'Watcher.count', 2 do
486 post :new, :project_id => 1,
486 post :new, :project_id => 1,
487 :issue => {:tracker_id => 1,
487 :issue => {:tracker_id => 1,
488 :subject => 'This is a new issue with watchers',
488 :subject => 'This is a new issue with watchers',
489 :description => 'This is the description',
489 :description => 'This is the description',
490 :priority_id => 5,
490 :priority_id => 5,
491 :watcher_user_ids => ['2', '3']}
491 :watcher_user_ids => ['2', '3']}
492 end
492 end
493 issue = Issue.find_by_subject('This is a new issue with watchers')
493 issue = Issue.find_by_subject('This is a new issue with watchers')
494 assert_not_nil issue
494 assert_not_nil issue
495 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
495 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
496
496
497 # Watchers added
497 # Watchers added
498 assert_equal [2, 3], issue.watcher_user_ids.sort
498 assert_equal [2, 3], issue.watcher_user_ids.sort
499 assert issue.watched_by?(User.find(3))
499 assert issue.watched_by?(User.find(3))
500 # Watchers notified
500 # Watchers notified
501 mail = ActionMailer::Base.deliveries.last
501 mail = ActionMailer::Base.deliveries.last
502 assert_kind_of TMail::Mail, mail
502 assert_kind_of TMail::Mail, mail
503 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
503 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
504 end
504 end
505
505
506 def test_post_new_should_send_a_notification
507 ActionMailer::Base.deliveries.clear
508 @request.session[:user_id] = 2
509 post :new, :project_id => 1,
510 :issue => {:tracker_id => 3,
511 :subject => 'This is the test_new issue',
512 :description => 'This is the description',
513 :priority_id => 5,
514 :estimated_hours => '',
515 :custom_field_values => {'2' => 'Value for field 2'}}
516 assert_redirected_to :action => 'show'
517
518 assert_equal 1, ActionMailer::Base.deliveries.size
519 end
520
506 def test_post_should_preserve_fields_values_on_validation_failure
521 def test_post_should_preserve_fields_values_on_validation_failure
507 @request.session[:user_id] = 2
522 @request.session[:user_id] = 2
508 post :new, :project_id => 1,
523 post :new, :project_id => 1,
509 :issue => {:tracker_id => 1,
524 :issue => {:tracker_id => 1,
510 # empty subject
525 # empty subject
511 :subject => '',
526 :subject => '',
512 :description => 'This is a description',
527 :description => 'This is a description',
513 :priority_id => 6,
528 :priority_id => 6,
514 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
529 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
515 assert_response :success
530 assert_response :success
516 assert_template 'new'
531 assert_template 'new'
517
532
518 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
533 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
519 :content => 'This is a description'
534 :content => 'This is a description'
520 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
535 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
521 :child => { :tag => 'option', :attributes => { :selected => 'selected',
536 :child => { :tag => 'option', :attributes => { :selected => 'selected',
522 :value => '6' },
537 :value => '6' },
523 :content => 'High' }
538 :content => 'High' }
524 # Custom fields
539 # Custom fields
525 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
540 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
526 :child => { :tag => 'option', :attributes => { :selected => 'selected',
541 :child => { :tag => 'option', :attributes => { :selected => 'selected',
527 :value => 'Oracle' },
542 :value => 'Oracle' },
528 :content => 'Oracle' }
543 :content => 'Oracle' }
529 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
544 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
530 :value => 'Value for field 2'}
545 :value => 'Value for field 2'}
531 end
546 end
532
547
533 def test_copy_routing
548 def test_copy_routing
534 assert_routing(
549 assert_routing(
535 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
550 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
536 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
551 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
537 )
552 )
538 end
553 end
539
554
540 def test_copy_issue
555 def test_copy_issue
541 @request.session[:user_id] = 2
556 @request.session[:user_id] = 2
542 get :new, :project_id => 1, :copy_from => 1
557 get :new, :project_id => 1, :copy_from => 1
543 assert_template 'new'
558 assert_template 'new'
544 assert_not_nil assigns(:issue)
559 assert_not_nil assigns(:issue)
545 orig = Issue.find(1)
560 orig = Issue.find(1)
546 assert_equal orig.subject, assigns(:issue).subject
561 assert_equal orig.subject, assigns(:issue).subject
547 end
562 end
548
563
549 def test_edit_routing
564 def test_edit_routing
550 assert_routing(
565 assert_routing(
551 {:method => :get, :path => '/issues/1/edit'},
566 {:method => :get, :path => '/issues/1/edit'},
552 :controller => 'issues', :action => 'edit', :id => '1'
567 :controller => 'issues', :action => 'edit', :id => '1'
553 )
568 )
554 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
569 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
555 {:controller => 'issues', :action => 'edit', :id => '1'},
570 {:controller => 'issues', :action => 'edit', :id => '1'},
556 {:method => :post, :path => '/issues/1/edit'}
571 {:method => :post, :path => '/issues/1/edit'}
557 )
572 )
558 end
573 end
559
574
560 def test_get_edit
575 def test_get_edit
561 @request.session[:user_id] = 2
576 @request.session[:user_id] = 2
562 get :edit, :id => 1
577 get :edit, :id => 1
563 assert_response :success
578 assert_response :success
564 assert_template 'edit'
579 assert_template 'edit'
565 assert_not_nil assigns(:issue)
580 assert_not_nil assigns(:issue)
566 assert_equal Issue.find(1), assigns(:issue)
581 assert_equal Issue.find(1), assigns(:issue)
567 end
582 end
568
583
569 def test_get_edit_with_params
584 def test_get_edit_with_params
570 @request.session[:user_id] = 2
585 @request.session[:user_id] = 2
571 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
586 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
572 assert_response :success
587 assert_response :success
573 assert_template 'edit'
588 assert_template 'edit'
574
589
575 issue = assigns(:issue)
590 issue = assigns(:issue)
576 assert_not_nil issue
591 assert_not_nil issue
577
592
578 assert_equal 5, issue.status_id
593 assert_equal 5, issue.status_id
579 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
594 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
580 :child => { :tag => 'option',
595 :child => { :tag => 'option',
581 :content => 'Closed',
596 :content => 'Closed',
582 :attributes => { :selected => 'selected' } }
597 :attributes => { :selected => 'selected' } }
583
598
584 assert_equal 7, issue.priority_id
599 assert_equal 7, issue.priority_id
585 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
600 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
586 :child => { :tag => 'option',
601 :child => { :tag => 'option',
587 :content => 'Urgent',
602 :content => 'Urgent',
588 :attributes => { :selected => 'selected' } }
603 :attributes => { :selected => 'selected' } }
589 end
604 end
590
605
591 def test_reply_routing
606 def test_reply_routing
592 assert_routing(
607 assert_routing(
593 {:method => :post, :path => '/issues/1/quoted'},
608 {:method => :post, :path => '/issues/1/quoted'},
594 :controller => 'issues', :action => 'reply', :id => '1'
609 :controller => 'issues', :action => 'reply', :id => '1'
595 )
610 )
596 end
611 end
597
612
598 def test_reply_to_issue
613 def test_reply_to_issue
599 @request.session[:user_id] = 2
614 @request.session[:user_id] = 2
600 get :reply, :id => 1
615 get :reply, :id => 1
601 assert_response :success
616 assert_response :success
602 assert_select_rjs :show, "update"
617 assert_select_rjs :show, "update"
603 end
618 end
604
619
605 def test_reply_to_note
620 def test_reply_to_note
606 @request.session[:user_id] = 2
621 @request.session[:user_id] = 2
607 get :reply, :id => 1, :journal_id => 2
622 get :reply, :id => 1, :journal_id => 2
608 assert_response :success
623 assert_response :success
609 assert_select_rjs :show, "update"
624 assert_select_rjs :show, "update"
610 end
625 end
611
626
612 def test_post_edit_without_custom_fields_param
627 def test_post_edit_without_custom_fields_param
613 @request.session[:user_id] = 2
628 @request.session[:user_id] = 2
614 ActionMailer::Base.deliveries.clear
629 ActionMailer::Base.deliveries.clear
615
630
616 issue = Issue.find(1)
631 issue = Issue.find(1)
617 assert_equal '125', issue.custom_value_for(2).value
632 assert_equal '125', issue.custom_value_for(2).value
618 old_subject = issue.subject
633 old_subject = issue.subject
619 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
634 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
620
635
621 assert_difference('Journal.count') do
636 assert_difference('Journal.count') do
622 assert_difference('JournalDetail.count', 2) do
637 assert_difference('JournalDetail.count', 2) do
623 post :edit, :id => 1, :issue => {:subject => new_subject,
638 post :edit, :id => 1, :issue => {:subject => new_subject,
624 :priority_id => '6',
639 :priority_id => '6',
625 :category_id => '1' # no change
640 :category_id => '1' # no change
626 }
641 }
627 end
642 end
628 end
643 end
629 assert_redirected_to :action => 'show', :id => '1'
644 assert_redirected_to :action => 'show', :id => '1'
630 issue.reload
645 issue.reload
631 assert_equal new_subject, issue.subject
646 assert_equal new_subject, issue.subject
632 # Make sure custom fields were not cleared
647 # Make sure custom fields were not cleared
633 assert_equal '125', issue.custom_value_for(2).value
648 assert_equal '125', issue.custom_value_for(2).value
634
649
635 mail = ActionMailer::Base.deliveries.last
650 mail = ActionMailer::Base.deliveries.last
636 assert_kind_of TMail::Mail, mail
651 assert_kind_of TMail::Mail, mail
637 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
652 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
638 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
653 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
639 end
654 end
640
655
641 def test_post_edit_with_custom_field_change
656 def test_post_edit_with_custom_field_change
642 @request.session[:user_id] = 2
657 @request.session[:user_id] = 2
643 issue = Issue.find(1)
658 issue = Issue.find(1)
644 assert_equal '125', issue.custom_value_for(2).value
659 assert_equal '125', issue.custom_value_for(2).value
645
660
646 assert_difference('Journal.count') do
661 assert_difference('Journal.count') do
647 assert_difference('JournalDetail.count', 3) do
662 assert_difference('JournalDetail.count', 3) do
648 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
663 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
649 :priority_id => '6',
664 :priority_id => '6',
650 :category_id => '1', # no change
665 :category_id => '1', # no change
651 :custom_field_values => { '2' => 'New custom value' }
666 :custom_field_values => { '2' => 'New custom value' }
652 }
667 }
653 end
668 end
654 end
669 end
655 assert_redirected_to :action => 'show', :id => '1'
670 assert_redirected_to :action => 'show', :id => '1'
656 issue.reload
671 issue.reload
657 assert_equal 'New custom value', issue.custom_value_for(2).value
672 assert_equal 'New custom value', issue.custom_value_for(2).value
658
673
659 mail = ActionMailer::Base.deliveries.last
674 mail = ActionMailer::Base.deliveries.last
660 assert_kind_of TMail::Mail, mail
675 assert_kind_of TMail::Mail, mail
661 assert mail.body.include?("Searchable field changed from 125 to New custom value")
676 assert mail.body.include?("Searchable field changed from 125 to New custom value")
662 end
677 end
663
678
664 def test_post_edit_with_status_and_assignee_change
679 def test_post_edit_with_status_and_assignee_change
665 issue = Issue.find(1)
680 issue = Issue.find(1)
666 assert_equal 1, issue.status_id
681 assert_equal 1, issue.status_id
667 @request.session[:user_id] = 2
682 @request.session[:user_id] = 2
668 assert_difference('TimeEntry.count', 0) do
683 assert_difference('TimeEntry.count', 0) do
669 post :edit,
684 post :edit,
670 :id => 1,
685 :id => 1,
671 :issue => { :status_id => 2, :assigned_to_id => 3 },
686 :issue => { :status_id => 2, :assigned_to_id => 3 },
672 :notes => 'Assigned to dlopper',
687 :notes => 'Assigned to dlopper',
673 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
688 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
674 end
689 end
675 assert_redirected_to :action => 'show', :id => '1'
690 assert_redirected_to :action => 'show', :id => '1'
676 issue.reload
691 issue.reload
677 assert_equal 2, issue.status_id
692 assert_equal 2, issue.status_id
678 j = issue.journals.find(:first, :order => 'id DESC')
693 j = issue.journals.find(:first, :order => 'id DESC')
679 assert_equal 'Assigned to dlopper', j.notes
694 assert_equal 'Assigned to dlopper', j.notes
680 assert_equal 2, j.details.size
695 assert_equal 2, j.details.size
681
696
682 mail = ActionMailer::Base.deliveries.last
697 mail = ActionMailer::Base.deliveries.last
683 assert mail.body.include?("Status changed from New to Assigned")
698 assert mail.body.include?("Status changed from New to Assigned")
684 end
699 end
685
700
686 def test_post_edit_with_note_only
701 def test_post_edit_with_note_only
687 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
702 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
688 # anonymous user
703 # anonymous user
689 post :edit,
704 post :edit,
690 :id => 1,
705 :id => 1,
691 :notes => notes
706 :notes => notes
692 assert_redirected_to :action => 'show', :id => '1'
707 assert_redirected_to :action => 'show', :id => '1'
693 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
708 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
694 assert_equal notes, j.notes
709 assert_equal notes, j.notes
695 assert_equal 0, j.details.size
710 assert_equal 0, j.details.size
696 assert_equal User.anonymous, j.user
711 assert_equal User.anonymous, j.user
697
712
698 mail = ActionMailer::Base.deliveries.last
713 mail = ActionMailer::Base.deliveries.last
699 assert mail.body.include?(notes)
714 assert mail.body.include?(notes)
700 end
715 end
701
716
702 def test_post_edit_with_note_and_spent_time
717 def test_post_edit_with_note_and_spent_time
703 @request.session[:user_id] = 2
718 @request.session[:user_id] = 2
704 spent_hours_before = Issue.find(1).spent_hours
719 spent_hours_before = Issue.find(1).spent_hours
705 assert_difference('TimeEntry.count') do
720 assert_difference('TimeEntry.count') do
706 post :edit,
721 post :edit,
707 :id => 1,
722 :id => 1,
708 :notes => '2.5 hours added',
723 :notes => '2.5 hours added',
709 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
724 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
710 end
725 end
711 assert_redirected_to :action => 'show', :id => '1'
726 assert_redirected_to :action => 'show', :id => '1'
712
727
713 issue = Issue.find(1)
728 issue = Issue.find(1)
714
729
715 j = issue.journals.find(:first, :order => 'id DESC')
730 j = issue.journals.find(:first, :order => 'id DESC')
716 assert_equal '2.5 hours added', j.notes
731 assert_equal '2.5 hours added', j.notes
717 assert_equal 0, j.details.size
732 assert_equal 0, j.details.size
718
733
719 t = issue.time_entries.find(:first, :order => 'id DESC')
734 t = issue.time_entries.find(:first, :order => 'id DESC')
720 assert_not_nil t
735 assert_not_nil t
721 assert_equal 2.5, t.hours
736 assert_equal 2.5, t.hours
722 assert_equal spent_hours_before + 2.5, issue.spent_hours
737 assert_equal spent_hours_before + 2.5, issue.spent_hours
723 end
738 end
724
739
725 def test_post_edit_with_attachment_only
740 def test_post_edit_with_attachment_only
726 set_tmp_attachments_directory
741 set_tmp_attachments_directory
727
742
728 # Delete all fixtured journals, a race condition can occur causing the wrong
743 # Delete all fixtured journals, a race condition can occur causing the wrong
729 # journal to get fetched in the next find.
744 # journal to get fetched in the next find.
730 Journal.delete_all
745 Journal.delete_all
731
746
732 # anonymous user
747 # anonymous user
733 post :edit,
748 post :edit,
734 :id => 1,
749 :id => 1,
735 :notes => '',
750 :notes => '',
736 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
751 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
737 assert_redirected_to :action => 'show', :id => '1'
752 assert_redirected_to :action => 'show', :id => '1'
738 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
753 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
739 assert j.notes.blank?
754 assert j.notes.blank?
740 assert_equal 1, j.details.size
755 assert_equal 1, j.details.size
741 assert_equal 'testfile.txt', j.details.first.value
756 assert_equal 'testfile.txt', j.details.first.value
742 assert_equal User.anonymous, j.user
757 assert_equal User.anonymous, j.user
743
758
744 mail = ActionMailer::Base.deliveries.last
759 mail = ActionMailer::Base.deliveries.last
745 assert mail.body.include?('testfile.txt')
760 assert mail.body.include?('testfile.txt')
746 end
761 end
747
762
748 def test_post_edit_with_no_change
763 def test_post_edit_with_no_change
749 issue = Issue.find(1)
764 issue = Issue.find(1)
750 issue.journals.clear
765 issue.journals.clear
751 ActionMailer::Base.deliveries.clear
766 ActionMailer::Base.deliveries.clear
752
767
753 post :edit,
768 post :edit,
754 :id => 1,
769 :id => 1,
755 :notes => ''
770 :notes => ''
756 assert_redirected_to :action => 'show', :id => '1'
771 assert_redirected_to :action => 'show', :id => '1'
757
772
758 issue.reload
773 issue.reload
759 assert issue.journals.empty?
774 assert issue.journals.empty?
760 # No email should be sent
775 # No email should be sent
761 assert ActionMailer::Base.deliveries.empty?
776 assert ActionMailer::Base.deliveries.empty?
762 end
777 end
763
778
779 def test_post_edit_should_send_a_notification
780 @request.session[:user_id] = 2
781 ActionMailer::Base.deliveries.clear
782 issue = Issue.find(1)
783 old_subject = issue.subject
784 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
785
786 post :edit, :id => 1, :issue => {:subject => new_subject,
787 :priority_id => '6',
788 :category_id => '1' # no change
789 }
790 assert_equal 1, ActionMailer::Base.deliveries.size
791 end
792
764 def test_post_edit_with_invalid_spent_time
793 def test_post_edit_with_invalid_spent_time
765 @request.session[:user_id] = 2
794 @request.session[:user_id] = 2
766 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
795 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
767
796
768 assert_no_difference('Journal.count') do
797 assert_no_difference('Journal.count') do
769 post :edit,
798 post :edit,
770 :id => 1,
799 :id => 1,
771 :notes => notes,
800 :notes => notes,
772 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
801 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
773 end
802 end
774 assert_response :success
803 assert_response :success
775 assert_template 'edit'
804 assert_template 'edit'
776
805
777 assert_tag :textarea, :attributes => { :name => 'notes' },
806 assert_tag :textarea, :attributes => { :name => 'notes' },
778 :content => notes
807 :content => notes
779 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
808 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
780 end
809 end
781
810
782 def test_bulk_edit
811 def test_bulk_edit
783 @request.session[:user_id] = 2
812 @request.session[:user_id] = 2
784 # update issues priority
813 # update issues priority
785 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
814 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
786 :assigned_to_id => '',
815 :assigned_to_id => '',
787 :custom_field_values => {'2' => ''},
816 :custom_field_values => {'2' => ''},
788 :notes => 'Bulk editing'
817 :notes => 'Bulk editing'
789 assert_response 302
818 assert_response 302
790 # check that the issues were updated
819 # check that the issues were updated
791 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
820 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
792
821
793 issue = Issue.find(1)
822 issue = Issue.find(1)
794 journal = issue.journals.find(:first, :order => 'created_on DESC')
823 journal = issue.journals.find(:first, :order => 'created_on DESC')
795 assert_equal '125', issue.custom_value_for(2).value
824 assert_equal '125', issue.custom_value_for(2).value
796 assert_equal 'Bulk editing', journal.notes
825 assert_equal 'Bulk editing', journal.notes
797 assert_equal 1, journal.details.size
826 assert_equal 1, journal.details.size
798 end
827 end
799
828
829 def test_bullk_edit_should_send_a_notification
830 @request.session[:user_id] = 2
831 ActionMailer::Base.deliveries.clear
832 post(:bulk_edit,
833 {
834 :ids => [1, 2],
835 :priority_id => 7,
836 :assigned_to_id => '',
837 :custom_field_values => {'2' => ''},
838 :notes => 'Bulk editing'
839 })
840
841 assert_response 302
842 assert_equal 2, ActionMailer::Base.deliveries.size
843 end
844
800 def test_bulk_edit_custom_field
845 def test_bulk_edit_custom_field
801 @request.session[:user_id] = 2
846 @request.session[:user_id] = 2
802 # update issues priority
847 # update issues priority
803 post :bulk_edit, :ids => [1, 2], :priority_id => '',
848 post :bulk_edit, :ids => [1, 2], :priority_id => '',
804 :assigned_to_id => '',
849 :assigned_to_id => '',
805 :custom_field_values => {'2' => '777'},
850 :custom_field_values => {'2' => '777'},
806 :notes => 'Bulk editing custom field'
851 :notes => 'Bulk editing custom field'
807 assert_response 302
852 assert_response 302
808
853
809 issue = Issue.find(1)
854 issue = Issue.find(1)
810 journal = issue.journals.find(:first, :order => 'created_on DESC')
855 journal = issue.journals.find(:first, :order => 'created_on DESC')
811 assert_equal '777', issue.custom_value_for(2).value
856 assert_equal '777', issue.custom_value_for(2).value
812 assert_equal 1, journal.details.size
857 assert_equal 1, journal.details.size
813 assert_equal '125', journal.details.first.old_value
858 assert_equal '125', journal.details.first.old_value
814 assert_equal '777', journal.details.first.value
859 assert_equal '777', journal.details.first.value
815 end
860 end
816
861
817 def test_bulk_unassign
862 def test_bulk_unassign
818 assert_not_nil Issue.find(2).assigned_to
863 assert_not_nil Issue.find(2).assigned_to
819 @request.session[:user_id] = 2
864 @request.session[:user_id] = 2
820 # unassign issues
865 # unassign issues
821 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
866 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
822 assert_response 302
867 assert_response 302
823 # check that the issues were updated
868 # check that the issues were updated
824 assert_nil Issue.find(2).assigned_to
869 assert_nil Issue.find(2).assigned_to
825 end
870 end
826
871
827 def test_move_routing
872 def test_move_routing
828 assert_routing(
873 assert_routing(
829 {:method => :get, :path => '/issues/1/move'},
874 {:method => :get, :path => '/issues/1/move'},
830 :controller => 'issues', :action => 'move', :id => '1'
875 :controller => 'issues', :action => 'move', :id => '1'
831 )
876 )
832 assert_recognizes(
877 assert_recognizes(
833 {:controller => 'issues', :action => 'move', :id => '1'},
878 {:controller => 'issues', :action => 'move', :id => '1'},
834 {:method => :post, :path => '/issues/1/move'}
879 {:method => :post, :path => '/issues/1/move'}
835 )
880 )
836 end
881 end
837
882
838 def test_move_one_issue_to_another_project
883 def test_move_one_issue_to_another_project
839 @request.session[:user_id] = 1
884 @request.session[:user_id] = 1
840 post :move, :id => 1, :new_project_id => 2
885 post :move, :id => 1, :new_project_id => 2
841 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
886 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
842 assert_equal 2, Issue.find(1).project_id
887 assert_equal 2, Issue.find(1).project_id
843 end
888 end
844
889
845 def test_bulk_move_to_another_project
890 def test_bulk_move_to_another_project
846 @request.session[:user_id] = 1
891 @request.session[:user_id] = 1
847 post :move, :ids => [1, 2], :new_project_id => 2
892 post :move, :ids => [1, 2], :new_project_id => 2
848 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
893 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
849 # Issues moved to project 2
894 # Issues moved to project 2
850 assert_equal 2, Issue.find(1).project_id
895 assert_equal 2, Issue.find(1).project_id
851 assert_equal 2, Issue.find(2).project_id
896 assert_equal 2, Issue.find(2).project_id
852 # No tracker change
897 # No tracker change
853 assert_equal 1, Issue.find(1).tracker_id
898 assert_equal 1, Issue.find(1).tracker_id
854 assert_equal 2, Issue.find(2).tracker_id
899 assert_equal 2, Issue.find(2).tracker_id
855 end
900 end
856
901
857 def test_bulk_move_to_another_tracker
902 def test_bulk_move_to_another_tracker
858 @request.session[:user_id] = 1
903 @request.session[:user_id] = 1
859 post :move, :ids => [1, 2], :new_tracker_id => 2
904 post :move, :ids => [1, 2], :new_tracker_id => 2
860 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
905 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
861 assert_equal 2, Issue.find(1).tracker_id
906 assert_equal 2, Issue.find(1).tracker_id
862 assert_equal 2, Issue.find(2).tracker_id
907 assert_equal 2, Issue.find(2).tracker_id
863 end
908 end
864
909
865 def test_bulk_copy_to_another_project
910 def test_bulk_copy_to_another_project
866 @request.session[:user_id] = 1
911 @request.session[:user_id] = 1
867 assert_difference 'Issue.count', 2 do
912 assert_difference 'Issue.count', 2 do
868 assert_no_difference 'Project.find(1).issues.count' do
913 assert_no_difference 'Project.find(1).issues.count' do
869 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
914 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
870 end
915 end
871 end
916 end
872 assert_redirected_to 'projects/ecookbook/issues'
917 assert_redirected_to 'projects/ecookbook/issues'
873 end
918 end
874
919
875 def test_context_menu_one_issue
920 def test_context_menu_one_issue
876 @request.session[:user_id] = 2
921 @request.session[:user_id] = 2
877 get :context_menu, :ids => [1]
922 get :context_menu, :ids => [1]
878 assert_response :success
923 assert_response :success
879 assert_template 'context_menu'
924 assert_template 'context_menu'
880 assert_tag :tag => 'a', :content => 'Edit',
925 assert_tag :tag => 'a', :content => 'Edit',
881 :attributes => { :href => '/issues/1/edit',
926 :attributes => { :href => '/issues/1/edit',
882 :class => 'icon-edit' }
927 :class => 'icon-edit' }
883 assert_tag :tag => 'a', :content => 'Closed',
928 assert_tag :tag => 'a', :content => 'Closed',
884 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
929 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
885 :class => '' }
930 :class => '' }
886 assert_tag :tag => 'a', :content => 'Immediate',
931 assert_tag :tag => 'a', :content => 'Immediate',
887 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
932 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
888 :class => '' }
933 :class => '' }
889 assert_tag :tag => 'a', :content => 'Dave Lopper',
934 assert_tag :tag => 'a', :content => 'Dave Lopper',
890 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
935 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
891 :class => '' }
936 :class => '' }
892 assert_tag :tag => 'a', :content => 'Copy',
937 assert_tag :tag => 'a', :content => 'Copy',
893 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
938 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
894 :class => 'icon-copy' }
939 :class => 'icon-copy' }
895 assert_tag :tag => 'a', :content => 'Move',
940 assert_tag :tag => 'a', :content => 'Move',
896 :attributes => { :href => '/issues/move?ids%5B%5D=1',
941 :attributes => { :href => '/issues/move?ids%5B%5D=1',
897 :class => 'icon-move' }
942 :class => 'icon-move' }
898 assert_tag :tag => 'a', :content => 'Delete',
943 assert_tag :tag => 'a', :content => 'Delete',
899 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
944 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
900 :class => 'icon-del' }
945 :class => 'icon-del' }
901 end
946 end
902
947
903 def test_context_menu_one_issue_by_anonymous
948 def test_context_menu_one_issue_by_anonymous
904 get :context_menu, :ids => [1]
949 get :context_menu, :ids => [1]
905 assert_response :success
950 assert_response :success
906 assert_template 'context_menu'
951 assert_template 'context_menu'
907 assert_tag :tag => 'a', :content => 'Delete',
952 assert_tag :tag => 'a', :content => 'Delete',
908 :attributes => { :href => '#',
953 :attributes => { :href => '#',
909 :class => 'icon-del disabled' }
954 :class => 'icon-del disabled' }
910 end
955 end
911
956
912 def test_context_menu_multiple_issues_of_same_project
957 def test_context_menu_multiple_issues_of_same_project
913 @request.session[:user_id] = 2
958 @request.session[:user_id] = 2
914 get :context_menu, :ids => [1, 2]
959 get :context_menu, :ids => [1, 2]
915 assert_response :success
960 assert_response :success
916 assert_template 'context_menu'
961 assert_template 'context_menu'
917 assert_tag :tag => 'a', :content => 'Edit',
962 assert_tag :tag => 'a', :content => 'Edit',
918 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
963 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
919 :class => 'icon-edit' }
964 :class => 'icon-edit' }
920 assert_tag :tag => 'a', :content => 'Immediate',
965 assert_tag :tag => 'a', :content => 'Immediate',
921 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
966 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
922 :class => '' }
967 :class => '' }
923 assert_tag :tag => 'a', :content => 'Dave Lopper',
968 assert_tag :tag => 'a', :content => 'Dave Lopper',
924 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
969 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
925 :class => '' }
970 :class => '' }
926 assert_tag :tag => 'a', :content => 'Move',
971 assert_tag :tag => 'a', :content => 'Move',
927 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
972 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
928 :class => 'icon-move' }
973 :class => 'icon-move' }
929 assert_tag :tag => 'a', :content => 'Delete',
974 assert_tag :tag => 'a', :content => 'Delete',
930 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
975 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
931 :class => 'icon-del' }
976 :class => 'icon-del' }
932 end
977 end
933
978
934 def test_context_menu_multiple_issues_of_different_project
979 def test_context_menu_multiple_issues_of_different_project
935 @request.session[:user_id] = 2
980 @request.session[:user_id] = 2
936 get :context_menu, :ids => [1, 2, 4]
981 get :context_menu, :ids => [1, 2, 4]
937 assert_response :success
982 assert_response :success
938 assert_template 'context_menu'
983 assert_template 'context_menu'
939 assert_tag :tag => 'a', :content => 'Delete',
984 assert_tag :tag => 'a', :content => 'Delete',
940 :attributes => { :href => '#',
985 :attributes => { :href => '#',
941 :class => 'icon-del disabled' }
986 :class => 'icon-del disabled' }
942 end
987 end
943
988
944 def test_destroy_routing
989 def test_destroy_routing
945 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
990 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
946 {:controller => 'issues', :action => 'destroy', :id => '1'},
991 {:controller => 'issues', :action => 'destroy', :id => '1'},
947 {:method => :post, :path => '/issues/1/destroy'}
992 {:method => :post, :path => '/issues/1/destroy'}
948 )
993 )
949 end
994 end
950
995
951 def test_destroy_issue_with_no_time_entries
996 def test_destroy_issue_with_no_time_entries
952 assert_nil TimeEntry.find_by_issue_id(2)
997 assert_nil TimeEntry.find_by_issue_id(2)
953 @request.session[:user_id] = 2
998 @request.session[:user_id] = 2
954 post :destroy, :id => 2
999 post :destroy, :id => 2
955 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1000 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
956 assert_nil Issue.find_by_id(2)
1001 assert_nil Issue.find_by_id(2)
957 end
1002 end
958
1003
959 def test_destroy_issues_with_time_entries
1004 def test_destroy_issues_with_time_entries
960 @request.session[:user_id] = 2
1005 @request.session[:user_id] = 2
961 post :destroy, :ids => [1, 3]
1006 post :destroy, :ids => [1, 3]
962 assert_response :success
1007 assert_response :success
963 assert_template 'destroy'
1008 assert_template 'destroy'
964 assert_not_nil assigns(:hours)
1009 assert_not_nil assigns(:hours)
965 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1010 assert Issue.find_by_id(1) && Issue.find_by_id(3)
966 end
1011 end
967
1012
968 def test_destroy_issues_and_destroy_time_entries
1013 def test_destroy_issues_and_destroy_time_entries
969 @request.session[:user_id] = 2
1014 @request.session[:user_id] = 2
970 post :destroy, :ids => [1, 3], :todo => 'destroy'
1015 post :destroy, :ids => [1, 3], :todo => 'destroy'
971 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1016 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
972 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1017 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
973 assert_nil TimeEntry.find_by_id([1, 2])
1018 assert_nil TimeEntry.find_by_id([1, 2])
974 end
1019 end
975
1020
976 def test_destroy_issues_and_assign_time_entries_to_project
1021 def test_destroy_issues_and_assign_time_entries_to_project
977 @request.session[:user_id] = 2
1022 @request.session[:user_id] = 2
978 post :destroy, :ids => [1, 3], :todo => 'nullify'
1023 post :destroy, :ids => [1, 3], :todo => 'nullify'
979 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1024 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
980 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1025 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
981 assert_nil TimeEntry.find(1).issue_id
1026 assert_nil TimeEntry.find(1).issue_id
982 assert_nil TimeEntry.find(2).issue_id
1027 assert_nil TimeEntry.find(2).issue_id
983 end
1028 end
984
1029
985 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1030 def test_destroy_issues_and_reassign_time_entries_to_another_issue
986 @request.session[:user_id] = 2
1031 @request.session[:user_id] = 2
987 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1032 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
988 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1033 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
989 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1034 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
990 assert_equal 2, TimeEntry.find(1).issue_id
1035 assert_equal 2, TimeEntry.find(1).issue_id
991 assert_equal 2, TimeEntry.find(2).issue_id
1036 assert_equal 2, TimeEntry.find(2).issue_id
992 end
1037 end
993 end
1038 end
@@ -1,211 +1,215
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'news_controller'
19 require 'news_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class NewsController; def rescue_action(e) raise e end; end
22 class NewsController; def rescue_action(e) raise e end; end
23
23
24 class NewsControllerTest < Test::Unit::TestCase
24 class NewsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :enabled_modules, :news, :comments
25 fixtures :projects, :users, :roles, :members, :enabled_modules, :news, :comments
26
26
27 def setup
27 def setup
28 @controller = NewsController.new
28 @controller = NewsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 end
32 end
33
33
34 def test_index_routing
34 def test_index_routing
35 assert_routing(
35 assert_routing(
36 {:method => :get, :path => '/news'},
36 {:method => :get, :path => '/news'},
37 :controller => 'news', :action => 'index'
37 :controller => 'news', :action => 'index'
38 )
38 )
39 end
39 end
40
40
41 def test_index_routing_formatted
41 def test_index_routing_formatted
42 assert_routing(
42 assert_routing(
43 {:method => :get, :path => '/news.atom'},
43 {:method => :get, :path => '/news.atom'},
44 :controller => 'news', :action => 'index', :format => 'atom'
44 :controller => 'news', :action => 'index', :format => 'atom'
45 )
45 )
46 end
46 end
47
47
48 def test_index
48 def test_index
49 get :index
49 get :index
50 assert_response :success
50 assert_response :success
51 assert_template 'index'
51 assert_template 'index'
52 assert_not_nil assigns(:newss)
52 assert_not_nil assigns(:newss)
53 assert_nil assigns(:project)
53 assert_nil assigns(:project)
54 end
54 end
55
55
56 def test_index_with_project_routing
56 def test_index_with_project_routing
57 assert_routing(
57 assert_routing(
58 {:method => :get, :path => '/projects/567/news'},
58 {:method => :get, :path => '/projects/567/news'},
59 :controller => 'news', :action => 'index', :project_id => '567'
59 :controller => 'news', :action => 'index', :project_id => '567'
60 )
60 )
61 end
61 end
62
62
63 def test_index_with_project_routing_formatted
63 def test_index_with_project_routing_formatted
64 assert_routing(
64 assert_routing(
65 {:method => :get, :path => '/projects/567/news.atom'},
65 {:method => :get, :path => '/projects/567/news.atom'},
66 :controller => 'news', :action => 'index', :project_id => '567', :format => 'atom'
66 :controller => 'news', :action => 'index', :project_id => '567', :format => 'atom'
67 )
67 )
68 end
68 end
69
69
70 def test_index_with_project
70 def test_index_with_project
71 get :index, :project_id => 1
71 get :index, :project_id => 1
72 assert_response :success
72 assert_response :success
73 assert_template 'index'
73 assert_template 'index'
74 assert_not_nil assigns(:newss)
74 assert_not_nil assigns(:newss)
75 end
75 end
76
76
77 def test_show_routing
77 def test_show_routing
78 assert_routing(
78 assert_routing(
79 {:method => :get, :path => '/news/2'},
79 {:method => :get, :path => '/news/2'},
80 :controller => 'news', :action => 'show', :id => '2'
80 :controller => 'news', :action => 'show', :id => '2'
81 )
81 )
82 end
82 end
83
83
84 def test_show
84 def test_show
85 get :show, :id => 1
85 get :show, :id => 1
86 assert_response :success
86 assert_response :success
87 assert_template 'show'
87 assert_template 'show'
88 assert_tag :tag => 'h2', :content => /eCookbook first release/
88 assert_tag :tag => 'h2', :content => /eCookbook first release/
89 end
89 end
90
90
91 def test_show_not_found
91 def test_show_not_found
92 get :show, :id => 999
92 get :show, :id => 999
93 assert_response 404
93 assert_response 404
94 end
94 end
95
95
96 def test_new_routing
96 def test_new_routing
97 assert_routing(
97 assert_routing(
98 {:method => :get, :path => '/projects/567/news/new'},
98 {:method => :get, :path => '/projects/567/news/new'},
99 :controller => 'news', :action => 'new', :project_id => '567'
99 :controller => 'news', :action => 'new', :project_id => '567'
100 )
100 )
101 assert_recognizes(
101 assert_recognizes(
102 {:controller => 'news', :action => 'new', :project_id => '567'},
102 {:controller => 'news', :action => 'new', :project_id => '567'},
103 {:method => :post, :path => '/projects/567/news'}
103 {:method => :post, :path => '/projects/567/news'}
104 )
104 )
105 end
105 end
106
106
107 def test_get_new
107 def test_get_new
108 @request.session[:user_id] = 2
108 @request.session[:user_id] = 2
109 get :new, :project_id => 1
109 get :new, :project_id => 1
110 assert_response :success
110 assert_response :success
111 assert_template 'new'
111 assert_template 'new'
112 end
112 end
113
113
114 def test_post_new
114 def test_post_new
115 ActionMailer::Base.deliveries.clear
116 Setting.notified_events << 'news_added'
117
115 @request.session[:user_id] = 2
118 @request.session[:user_id] = 2
116 post :new, :project_id => 1, :news => { :title => 'NewsControllerTest',
119 post :new, :project_id => 1, :news => { :title => 'NewsControllerTest',
117 :description => 'This is the description',
120 :description => 'This is the description',
118 :summary => '' }
121 :summary => '' }
119 assert_redirected_to 'projects/ecookbook/news'
122 assert_redirected_to 'projects/ecookbook/news'
120
123
121 news = News.find_by_title('NewsControllerTest')
124 news = News.find_by_title('NewsControllerTest')
122 assert_not_nil news
125 assert_not_nil news
123 assert_equal 'This is the description', news.description
126 assert_equal 'This is the description', news.description
124 assert_equal User.find(2), news.author
127 assert_equal User.find(2), news.author
125 assert_equal Project.find(1), news.project
128 assert_equal Project.find(1), news.project
129 assert_equal 1, ActionMailer::Base.deliveries.size
126 end
130 end
127
131
128 def test_edit_routing
132 def test_edit_routing
129 assert_routing(
133 assert_routing(
130 {:method => :get, :path => '/news/234'},
134 {:method => :get, :path => '/news/234'},
131 :controller => 'news', :action => 'show', :id => '234'
135 :controller => 'news', :action => 'show', :id => '234'
132 )
136 )
133 assert_recognizes(#TODO: PUT to news URI instead, need to modify form
137 assert_recognizes(#TODO: PUT to news URI instead, need to modify form
134 {:controller => 'news', :action => 'edit', :id => '567'},
138 {:controller => 'news', :action => 'edit', :id => '567'},
135 {:method => :post, :path => '/news/567/edit'}
139 {:method => :post, :path => '/news/567/edit'}
136 )
140 )
137 end
141 end
138
142
139 def test_get_edit
143 def test_get_edit
140 @request.session[:user_id] = 2
144 @request.session[:user_id] = 2
141 get :edit, :id => 1
145 get :edit, :id => 1
142 assert_response :success
146 assert_response :success
143 assert_template 'edit'
147 assert_template 'edit'
144 end
148 end
145
149
146 def test_post_edit
150 def test_post_edit
147 @request.session[:user_id] = 2
151 @request.session[:user_id] = 2
148 post :edit, :id => 1, :news => { :description => 'Description changed by test_post_edit' }
152 post :edit, :id => 1, :news => { :description => 'Description changed by test_post_edit' }
149 assert_redirected_to 'news/1'
153 assert_redirected_to 'news/1'
150 news = News.find(1)
154 news = News.find(1)
151 assert_equal 'Description changed by test_post_edit', news.description
155 assert_equal 'Description changed by test_post_edit', news.description
152 end
156 end
153
157
154 def test_post_new_with_validation_failure
158 def test_post_new_with_validation_failure
155 @request.session[:user_id] = 2
159 @request.session[:user_id] = 2
156 post :new, :project_id => 1, :news => { :title => '',
160 post :new, :project_id => 1, :news => { :title => '',
157 :description => 'This is the description',
161 :description => 'This is the description',
158 :summary => '' }
162 :summary => '' }
159 assert_response :success
163 assert_response :success
160 assert_template 'new'
164 assert_template 'new'
161 assert_not_nil assigns(:news)
165 assert_not_nil assigns(:news)
162 assert assigns(:news).new_record?
166 assert assigns(:news).new_record?
163 assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation' },
167 assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation' },
164 :content => /1 error/
168 :content => /1 error/
165 end
169 end
166
170
167 def test_add_comment
171 def test_add_comment
168 @request.session[:user_id] = 2
172 @request.session[:user_id] = 2
169 post :add_comment, :id => 1, :comment => { :comments => 'This is a NewsControllerTest comment' }
173 post :add_comment, :id => 1, :comment => { :comments => 'This is a NewsControllerTest comment' }
170 assert_redirected_to 'news/1'
174 assert_redirected_to 'news/1'
171
175
172 comment = News.find(1).comments.find(:first, :order => 'created_on DESC')
176 comment = News.find(1).comments.find(:first, :order => 'created_on DESC')
173 assert_not_nil comment
177 assert_not_nil comment
174 assert_equal 'This is a NewsControllerTest comment', comment.comments
178 assert_equal 'This is a NewsControllerTest comment', comment.comments
175 assert_equal User.find(2), comment.author
179 assert_equal User.find(2), comment.author
176 end
180 end
177
181
178 def test_destroy_comment
182 def test_destroy_comment
179 comments_count = News.find(1).comments.size
183 comments_count = News.find(1).comments.size
180 @request.session[:user_id] = 2
184 @request.session[:user_id] = 2
181 post :destroy_comment, :id => 1, :comment_id => 2
185 post :destroy_comment, :id => 1, :comment_id => 2
182 assert_redirected_to 'news/1'
186 assert_redirected_to 'news/1'
183 assert_nil Comment.find_by_id(2)
187 assert_nil Comment.find_by_id(2)
184 assert_equal comments_count - 1, News.find(1).comments.size
188 assert_equal comments_count - 1, News.find(1).comments.size
185 end
189 end
186
190
187 def test_destroy_routing
191 def test_destroy_routing
188 assert_recognizes(#TODO: should use DELETE to news URI, need to change form
192 assert_recognizes(#TODO: should use DELETE to news URI, need to change form
189 {:controller => 'news', :action => 'destroy', :id => '567'},
193 {:controller => 'news', :action => 'destroy', :id => '567'},
190 {:method => :post, :path => '/news/567/destroy'}
194 {:method => :post, :path => '/news/567/destroy'}
191 )
195 )
192 end
196 end
193
197
194 def test_destroy
198 def test_destroy
195 @request.session[:user_id] = 2
199 @request.session[:user_id] = 2
196 post :destroy, :id => 1
200 post :destroy, :id => 1
197 assert_redirected_to 'projects/ecookbook/news'
201 assert_redirected_to 'projects/ecookbook/news'
198 assert_nil News.find_by_id(1)
202 assert_nil News.find_by_id(1)
199 end
203 end
200
204
201 def test_preview
205 def test_preview
202 get :preview, :project_id => 1,
206 get :preview, :project_id => 1,
203 :news => {:title => '',
207 :news => {:title => '',
204 :description => 'News description',
208 :description => 'News description',
205 :summary => ''}
209 :summary => ''}
206 assert_response :success
210 assert_response :success
207 assert_template 'common/_preview'
211 assert_template 'common/_preview'
208 assert_tag :tag => 'fieldset', :attributes => { :class => 'preview' },
212 assert_tag :tag => 'fieldset', :attributes => { :class => 'preview' },
209 :content => /News description/
213 :content => /News description/
210 end
214 end
211 end
215 end
@@ -1,73 +1,75
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ChangesetTest < Test::Unit::TestCase
20 class ChangesetTest < Test::Unit::TestCase
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :trackers
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :trackers
22
22
23 def setup
23 def setup
24 end
24 end
25
25
26 def test_ref_keywords_any
26 def test_ref_keywords_any
27 ActionMailer::Base.deliveries.clear
27 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
28 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
28 Setting.commit_fix_done_ratio = '90'
29 Setting.commit_fix_done_ratio = '90'
29 Setting.commit_ref_keywords = '*'
30 Setting.commit_ref_keywords = '*'
30 Setting.commit_fix_keywords = 'fixes , closes'
31 Setting.commit_fix_keywords = 'fixes , closes'
31
32
32 c = Changeset.new(:repository => Project.find(1).repository,
33 c = Changeset.new(:repository => Project.find(1).repository,
33 :committed_on => Time.now,
34 :committed_on => Time.now,
34 :comments => 'New commit (#2). Fixes #1')
35 :comments => 'New commit (#2). Fixes #1')
35 c.scan_comment_for_issue_ids
36 c.scan_comment_for_issue_ids
36
37
37 assert_equal [1, 2], c.issue_ids.sort
38 assert_equal [1, 2], c.issue_ids.sort
38 fixed = Issue.find(1)
39 fixed = Issue.find(1)
39 assert fixed.closed?
40 assert fixed.closed?
40 assert_equal 90, fixed.done_ratio
41 assert_equal 90, fixed.done_ratio
42 assert_equal 1, ActionMailer::Base.deliveries.size
41 end
43 end
42
44
43 def test_ref_keywords_any_line_start
45 def test_ref_keywords_any_line_start
44 Setting.commit_ref_keywords = '*'
46 Setting.commit_ref_keywords = '*'
45
47
46 c = Changeset.new(:repository => Project.find(1).repository,
48 c = Changeset.new(:repository => Project.find(1).repository,
47 :committed_on => Time.now,
49 :committed_on => Time.now,
48 :comments => '#1 is the reason of this commit')
50 :comments => '#1 is the reason of this commit')
49 c.scan_comment_for_issue_ids
51 c.scan_comment_for_issue_ids
50
52
51 assert_equal [1], c.issue_ids.sort
53 assert_equal [1], c.issue_ids.sort
52 end
54 end
53
55
54 def test_previous
56 def test_previous
55 changeset = Changeset.find_by_revision('3')
57 changeset = Changeset.find_by_revision('3')
56 assert_equal Changeset.find_by_revision('2'), changeset.previous
58 assert_equal Changeset.find_by_revision('2'), changeset.previous
57 end
59 end
58
60
59 def test_previous_nil
61 def test_previous_nil
60 changeset = Changeset.find_by_revision('1')
62 changeset = Changeset.find_by_revision('1')
61 assert_nil changeset.previous
63 assert_nil changeset.previous
62 end
64 end
63
65
64 def test_next
66 def test_next
65 changeset = Changeset.find_by_revision('2')
67 changeset = Changeset.find_by_revision('2')
66 assert_equal Changeset.find_by_revision('3'), changeset.next
68 assert_equal Changeset.find_by_revision('3'), changeset.next
67 end
69 end
68
70
69 def test_next_nil
71 def test_next_nil
70 changeset = Changeset.find_by_revision('4')
72 changeset = Changeset.find_by_revision('4')
71 assert_nil changeset.next
73 assert_nil changeset.next
72 end
74 end
73 end
75 end
@@ -1,37 +1,46
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class DocumentTest < Test::Unit::TestCase
20 class DocumentTest < Test::Unit::TestCase
21 fixtures :projects, :enumerations, :documents
21 fixtures :projects, :enumerations, :documents
22
22
23 def test_create
23 def test_create
24 doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation'))
24 doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation'))
25 assert doc.save
25 assert doc.save
26 end
26 end
27
27
28 def test_create_should_send_email_notification
29 ActionMailer::Base.deliveries.clear
30 Setting.notified_events << 'document_added'
31 doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation'))
32
33 assert doc.save
34 assert_equal 1, ActionMailer::Base.deliveries.size
35 end
36
28 def test_create_with_default_category
37 def test_create_with_default_category
29 # Sets a default category
38 # Sets a default category
30 e = Enumeration.find_by_name('Technical documentation')
39 e = Enumeration.find_by_name('Technical documentation')
31 e.update_attributes(:is_default => true)
40 e.update_attributes(:is_default => true)
32
41
33 doc = Document.new(:project => Project.find(1), :title => 'New document')
42 doc = Document.new(:project => Project.find(1), :title => 'New document')
34 assert_equal e, doc.category
43 assert_equal e, doc.category
35 assert doc.save
44 assert doc.save
36 end
45 end
37 end
46 end
@@ -1,244 +1,252
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class IssueTest < Test::Unit::TestCase
20 class IssueTest < Test::Unit::TestCase
21 fixtures :projects, :users, :members,
21 fixtures :projects, :users, :members,
22 :trackers, :projects_trackers,
22 :trackers, :projects_trackers,
23 :issue_statuses, :issue_categories,
23 :issue_statuses, :issue_categories,
24 :enumerations,
24 :enumerations,
25 :issues,
25 :issues,
26 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
26 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
27 :time_entries
27 :time_entries
28
28
29 def test_create
29 def test_create
30 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
30 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
31 assert issue.save
31 assert issue.save
32 issue.reload
32 issue.reload
33 assert_equal 1.5, issue.estimated_hours
33 assert_equal 1.5, issue.estimated_hours
34 end
34 end
35
35
36 def test_create_minimal
36 def test_create_minimal
37 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create')
37 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create')
38 assert issue.save
38 assert issue.save
39 assert issue.description.nil?
39 assert issue.description.nil?
40 end
40 end
41
41
42 def test_create_with_required_custom_field
42 def test_create_with_required_custom_field
43 field = IssueCustomField.find_by_name('Database')
43 field = IssueCustomField.find_by_name('Database')
44 field.update_attribute(:is_required, true)
44 field.update_attribute(:is_required, true)
45
45
46 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
46 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
47 assert issue.available_custom_fields.include?(field)
47 assert issue.available_custom_fields.include?(field)
48 # No value for the custom field
48 # No value for the custom field
49 assert !issue.save
49 assert !issue.save
50 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
50 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
51 # Blank value
51 # Blank value
52 issue.custom_field_values = { field.id => '' }
52 issue.custom_field_values = { field.id => '' }
53 assert !issue.save
53 assert !issue.save
54 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
54 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
55 # Invalid value
55 # Invalid value
56 issue.custom_field_values = { field.id => 'SQLServer' }
56 issue.custom_field_values = { field.id => 'SQLServer' }
57 assert !issue.save
57 assert !issue.save
58 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
58 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
59 # Valid value
59 # Valid value
60 issue.custom_field_values = { field.id => 'PostgreSQL' }
60 issue.custom_field_values = { field.id => 'PostgreSQL' }
61 assert issue.save
61 assert issue.save
62 issue.reload
62 issue.reload
63 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
63 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
64 end
64 end
65
65
66 def test_errors_full_messages_should_include_custom_fields_errors
66 def test_errors_full_messages_should_include_custom_fields_errors
67 field = IssueCustomField.find_by_name('Database')
67 field = IssueCustomField.find_by_name('Database')
68
68
69 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
69 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
70 assert issue.available_custom_fields.include?(field)
70 assert issue.available_custom_fields.include?(field)
71 # Invalid value
71 # Invalid value
72 issue.custom_field_values = { field.id => 'SQLServer' }
72 issue.custom_field_values = { field.id => 'SQLServer' }
73
73
74 assert !issue.valid?
74 assert !issue.valid?
75 assert_equal 1, issue.errors.full_messages.size
75 assert_equal 1, issue.errors.full_messages.size
76 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
76 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
77 end
77 end
78
78
79 def test_update_issue_with_required_custom_field
79 def test_update_issue_with_required_custom_field
80 field = IssueCustomField.find_by_name('Database')
80 field = IssueCustomField.find_by_name('Database')
81 field.update_attribute(:is_required, true)
81 field.update_attribute(:is_required, true)
82
82
83 issue = Issue.find(1)
83 issue = Issue.find(1)
84 assert_nil issue.custom_value_for(field)
84 assert_nil issue.custom_value_for(field)
85 assert issue.available_custom_fields.include?(field)
85 assert issue.available_custom_fields.include?(field)
86 # No change to custom values, issue can be saved
86 # No change to custom values, issue can be saved
87 assert issue.save
87 assert issue.save
88 # Blank value
88 # Blank value
89 issue.custom_field_values = { field.id => '' }
89 issue.custom_field_values = { field.id => '' }
90 assert !issue.save
90 assert !issue.save
91 # Valid value
91 # Valid value
92 issue.custom_field_values = { field.id => 'PostgreSQL' }
92 issue.custom_field_values = { field.id => 'PostgreSQL' }
93 assert issue.save
93 assert issue.save
94 issue.reload
94 issue.reload
95 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
95 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
96 end
96 end
97
97
98 def test_should_not_update_attributes_if_custom_fields_validation_fails
98 def test_should_not_update_attributes_if_custom_fields_validation_fails
99 issue = Issue.find(1)
99 issue = Issue.find(1)
100 field = IssueCustomField.find_by_name('Database')
100 field = IssueCustomField.find_by_name('Database')
101 assert issue.available_custom_fields.include?(field)
101 assert issue.available_custom_fields.include?(field)
102
102
103 issue.custom_field_values = { field.id => 'Invalid' }
103 issue.custom_field_values = { field.id => 'Invalid' }
104 issue.subject = 'Should be not be saved'
104 issue.subject = 'Should be not be saved'
105 assert !issue.save
105 assert !issue.save
106
106
107 issue.reload
107 issue.reload
108 assert_equal "Can't print recipes", issue.subject
108 assert_equal "Can't print recipes", issue.subject
109 end
109 end
110
110
111 def test_should_not_recreate_custom_values_objects_on_update
111 def test_should_not_recreate_custom_values_objects_on_update
112 field = IssueCustomField.find_by_name('Database')
112 field = IssueCustomField.find_by_name('Database')
113
113
114 issue = Issue.find(1)
114 issue = Issue.find(1)
115 issue.custom_field_values = { field.id => 'PostgreSQL' }
115 issue.custom_field_values = { field.id => 'PostgreSQL' }
116 assert issue.save
116 assert issue.save
117 custom_value = issue.custom_value_for(field)
117 custom_value = issue.custom_value_for(field)
118 issue.reload
118 issue.reload
119 issue.custom_field_values = { field.id => 'MySQL' }
119 issue.custom_field_values = { field.id => 'MySQL' }
120 assert issue.save
120 assert issue.save
121 issue.reload
121 issue.reload
122 assert_equal custom_value.id, issue.custom_value_for(field).id
122 assert_equal custom_value.id, issue.custom_value_for(field).id
123 end
123 end
124
124
125 def test_category_based_assignment
125 def test_category_based_assignment
126 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
126 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
127 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
127 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
128 end
128 end
129
129
130 def test_copy
130 def test_copy
131 issue = Issue.new.copy_from(1)
131 issue = Issue.new.copy_from(1)
132 assert issue.save
132 assert issue.save
133 issue.reload
133 issue.reload
134 orig = Issue.find(1)
134 orig = Issue.find(1)
135 assert_equal orig.subject, issue.subject
135 assert_equal orig.subject, issue.subject
136 assert_equal orig.tracker, issue.tracker
136 assert_equal orig.tracker, issue.tracker
137 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
137 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
138 end
138 end
139
139
140 def test_should_close_duplicates
140 def test_should_close_duplicates
141 # Create 3 issues
141 # Create 3 issues
142 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
142 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
143 assert issue1.save
143 assert issue1.save
144 issue2 = issue1.clone
144 issue2 = issue1.clone
145 assert issue2.save
145 assert issue2.save
146 issue3 = issue1.clone
146 issue3 = issue1.clone
147 assert issue3.save
147 assert issue3.save
148
148
149 # 2 is a dupe of 1
149 # 2 is a dupe of 1
150 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
150 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
151 # And 3 is a dupe of 2
151 # And 3 is a dupe of 2
152 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
152 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
153 # And 3 is a dupe of 1 (circular duplicates)
153 # And 3 is a dupe of 1 (circular duplicates)
154 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
154 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
155
155
156 assert issue1.reload.duplicates.include?(issue2)
156 assert issue1.reload.duplicates.include?(issue2)
157
157
158 # Closing issue 1
158 # Closing issue 1
159 issue1.init_journal(User.find(:first), "Closing issue1")
159 issue1.init_journal(User.find(:first), "Closing issue1")
160 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
160 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
161 assert issue1.save
161 assert issue1.save
162 # 2 and 3 should be also closed
162 # 2 and 3 should be also closed
163 assert issue2.reload.closed?
163 assert issue2.reload.closed?
164 assert issue3.reload.closed?
164 assert issue3.reload.closed?
165 end
165 end
166
166
167 def test_should_not_close_duplicated_issue
167 def test_should_not_close_duplicated_issue
168 # Create 3 issues
168 # Create 3 issues
169 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
169 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
170 assert issue1.save
170 assert issue1.save
171 issue2 = issue1.clone
171 issue2 = issue1.clone
172 assert issue2.save
172 assert issue2.save
173
173
174 # 2 is a dupe of 1
174 # 2 is a dupe of 1
175 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
175 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
176 # 2 is a dup of 1 but 1 is not a duplicate of 2
176 # 2 is a dup of 1 but 1 is not a duplicate of 2
177 assert !issue2.reload.duplicates.include?(issue1)
177 assert !issue2.reload.duplicates.include?(issue1)
178
178
179 # Closing issue 2
179 # Closing issue 2
180 issue2.init_journal(User.find(:first), "Closing issue2")
180 issue2.init_journal(User.find(:first), "Closing issue2")
181 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
181 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
182 assert issue2.save
182 assert issue2.save
183 # 1 should not be also closed
183 # 1 should not be also closed
184 assert !issue1.reload.closed?
184 assert !issue1.reload.closed?
185 end
185 end
186
186
187 def test_move_to_another_project_with_same_category
187 def test_move_to_another_project_with_same_category
188 issue = Issue.find(1)
188 issue = Issue.find(1)
189 assert issue.move_to(Project.find(2))
189 assert issue.move_to(Project.find(2))
190 issue.reload
190 issue.reload
191 assert_equal 2, issue.project_id
191 assert_equal 2, issue.project_id
192 # Category changes
192 # Category changes
193 assert_equal 4, issue.category_id
193 assert_equal 4, issue.category_id
194 # Make sure time entries were move to the target project
194 # Make sure time entries were move to the target project
195 assert_equal 2, issue.time_entries.first.project_id
195 assert_equal 2, issue.time_entries.first.project_id
196 end
196 end
197
197
198 def test_move_to_another_project_without_same_category
198 def test_move_to_another_project_without_same_category
199 issue = Issue.find(2)
199 issue = Issue.find(2)
200 assert issue.move_to(Project.find(2))
200 assert issue.move_to(Project.find(2))
201 issue.reload
201 issue.reload
202 assert_equal 2, issue.project_id
202 assert_equal 2, issue.project_id
203 # Category cleared
203 # Category cleared
204 assert_nil issue.category_id
204 assert_nil issue.category_id
205 end
205 end
206
206
207 def test_copy_to_the_same_project
207 def test_copy_to_the_same_project
208 issue = Issue.find(1)
208 issue = Issue.find(1)
209 copy = nil
209 copy = nil
210 assert_difference 'Issue.count' do
210 assert_difference 'Issue.count' do
211 copy = issue.move_to(issue.project, nil, :copy => true)
211 copy = issue.move_to(issue.project, nil, :copy => true)
212 end
212 end
213 assert_kind_of Issue, copy
213 assert_kind_of Issue, copy
214 assert_equal issue.project, copy.project
214 assert_equal issue.project, copy.project
215 assert_equal "125", copy.custom_value_for(2).value
215 assert_equal "125", copy.custom_value_for(2).value
216 end
216 end
217
217
218 def test_copy_to_another_project_and_tracker
218 def test_copy_to_another_project_and_tracker
219 issue = Issue.find(1)
219 issue = Issue.find(1)
220 copy = nil
220 copy = nil
221 assert_difference 'Issue.count' do
221 assert_difference 'Issue.count' do
222 copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
222 copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
223 end
223 end
224 assert_kind_of Issue, copy
224 assert_kind_of Issue, copy
225 assert_equal Project.find(3), copy.project
225 assert_equal Project.find(3), copy.project
226 assert_equal Tracker.find(2), copy.tracker
226 assert_equal Tracker.find(2), copy.tracker
227 # Custom field #2 is not associated with target tracker
227 # Custom field #2 is not associated with target tracker
228 assert_nil copy.custom_value_for(2)
228 assert_nil copy.custom_value_for(2)
229 end
229 end
230
230
231 def test_issue_destroy
231 def test_issue_destroy
232 Issue.find(1).destroy
232 Issue.find(1).destroy
233 assert_nil Issue.find_by_id(1)
233 assert_nil Issue.find_by_id(1)
234 assert_nil TimeEntry.find_by_issue_id(1)
234 assert_nil TimeEntry.find_by_issue_id(1)
235 end
235 end
236
236
237 def test_overdue
237 def test_overdue
238 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
238 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
239 assert !Issue.new(:due_date => Date.today).overdue?
239 assert !Issue.new(:due_date => Date.today).overdue?
240 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
240 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
241 assert !Issue.new(:due_date => nil).overdue?
241 assert !Issue.new(:due_date => nil).overdue?
242 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
242 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
243 end
243 end
244
245 def test_create_should_send_email_notification
246 ActionMailer::Base.deliveries.clear
247 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create', :estimated_hours => '1:30')
248
249 assert issue.save
250 assert_equal 1, ActionMailer::Base.deliveries.size
251 end
244 end
252 end
@@ -1,39 +1,50
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class JournalTest < Test::Unit::TestCase
20 class JournalTest < Test::Unit::TestCase
21 fixtures :issues, :issue_statuses, :journals, :journal_details
21 fixtures :issues, :issue_statuses, :journals, :journal_details
22
22
23 def setup
23 def setup
24 @journal = Journal.find 1
24 @journal = Journal.find 1
25 end
25 end
26
26
27 def test_journalized_is_an_issue
27 def test_journalized_is_an_issue
28 issue = @journal.issue
28 issue = @journal.issue
29 assert_kind_of Issue, issue
29 assert_kind_of Issue, issue
30 assert_equal 1, issue.id
30 assert_equal 1, issue.id
31 end
31 end
32
32
33 def test_new_status
33 def test_new_status
34 status = @journal.new_status
34 status = @journal.new_status
35 assert_not_nil status
35 assert_not_nil status
36 assert_kind_of IssueStatus, status
36 assert_kind_of IssueStatus, status
37 assert_equal 2, status.id
37 assert_equal 2, status.id
38 end
38 end
39
40 def test_create_should_send_email_notification
41 ActionMailer::Base.deliveries.clear
42 issue = Issue.find(:first)
43 user = User.find(:first)
44 journal = issue.init_journal(user, issue)
45
46 assert journal.save
47 assert_equal 1, ActionMailer::Base.deliveries.size
48 end
49
39 end
50 end
@@ -1,190 +1,205
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class MailHandlerTest < Test::Unit::TestCase
20 class MailHandlerTest < Test::Unit::TestCase
21 fixtures :users, :projects,
21 fixtures :users, :projects,
22 :enabled_modules,
22 :enabled_modules,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :issues,
25 :issues,
26 :issue_statuses,
26 :issue_statuses,
27 :workflows,
27 :workflows,
28 :trackers,
28 :trackers,
29 :projects_trackers,
29 :projects_trackers,
30 :enumerations,
30 :enumerations,
31 :issue_categories,
31 :issue_categories,
32 :custom_fields,
32 :custom_fields,
33 :custom_fields_trackers,
33 :custom_fields_trackers,
34 :boards,
34 :boards,
35 :messages
35 :messages
36
36
37 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
37 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
38
38
39 def setup
39 def setup
40 ActionMailer::Base.deliveries.clear
40 ActionMailer::Base.deliveries.clear
41 end
41 end
42
42
43 def test_add_issue
43 def test_add_issue
44 # This email contains: 'Project: onlinestore'
44 # This email contains: 'Project: onlinestore'
45 issue = submit_email('ticket_on_given_project.eml')
45 issue = submit_email('ticket_on_given_project.eml')
46 assert issue.is_a?(Issue)
46 assert issue.is_a?(Issue)
47 assert !issue.new_record?
47 assert !issue.new_record?
48 issue.reload
48 issue.reload
49 assert_equal 'New ticket on a given project', issue.subject
49 assert_equal 'New ticket on a given project', issue.subject
50 assert_equal User.find_by_login('jsmith'), issue.author
50 assert_equal User.find_by_login('jsmith'), issue.author
51 assert_equal Project.find(2), issue.project
51 assert_equal Project.find(2), issue.project
52 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
52 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
53 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
53 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
54 # keywords should be removed from the email body
54 # keywords should be removed from the email body
55 assert !issue.description.match(/^Project:/i)
55 assert !issue.description.match(/^Project:/i)
56 assert !issue.description.match(/^Status:/i)
56 assert !issue.description.match(/^Status:/i)
57 end
57 end
58
58
59 def test_add_issue_with_status
59 def test_add_issue_with_status
60 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
60 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
61 issue = submit_email('ticket_on_given_project.eml')
61 issue = submit_email('ticket_on_given_project.eml')
62 assert issue.is_a?(Issue)
62 assert issue.is_a?(Issue)
63 assert !issue.new_record?
63 assert !issue.new_record?
64 issue.reload
64 issue.reload
65 assert_equal Project.find(2), issue.project
65 assert_equal Project.find(2), issue.project
66 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
66 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
67 end
67 end
68
68
69 def test_add_issue_with_attributes_override
69 def test_add_issue_with_attributes_override
70 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
70 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
71 assert issue.is_a?(Issue)
71 assert issue.is_a?(Issue)
72 assert !issue.new_record?
72 assert !issue.new_record?
73 issue.reload
73 issue.reload
74 assert_equal 'New ticket on a given project', issue.subject
74 assert_equal 'New ticket on a given project', issue.subject
75 assert_equal User.find_by_login('jsmith'), issue.author
75 assert_equal User.find_by_login('jsmith'), issue.author
76 assert_equal Project.find(2), issue.project
76 assert_equal Project.find(2), issue.project
77 assert_equal 'Feature request', issue.tracker.to_s
77 assert_equal 'Feature request', issue.tracker.to_s
78 assert_equal 'Stock management', issue.category.to_s
78 assert_equal 'Stock management', issue.category.to_s
79 assert_equal 'Urgent', issue.priority.to_s
79 assert_equal 'Urgent', issue.priority.to_s
80 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
80 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
81 end
81 end
82
82
83 def test_add_issue_with_partial_attributes_override
83 def test_add_issue_with_partial_attributes_override
84 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
84 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
85 assert issue.is_a?(Issue)
85 assert issue.is_a?(Issue)
86 assert !issue.new_record?
86 assert !issue.new_record?
87 issue.reload
87 issue.reload
88 assert_equal 'New ticket on a given project', issue.subject
88 assert_equal 'New ticket on a given project', issue.subject
89 assert_equal User.find_by_login('jsmith'), issue.author
89 assert_equal User.find_by_login('jsmith'), issue.author
90 assert_equal Project.find(2), issue.project
90 assert_equal Project.find(2), issue.project
91 assert_equal 'Feature request', issue.tracker.to_s
91 assert_equal 'Feature request', issue.tracker.to_s
92 assert_nil issue.category
92 assert_nil issue.category
93 assert_equal 'High', issue.priority.to_s
93 assert_equal 'High', issue.priority.to_s
94 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
94 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
95 end
95 end
96
96
97 def test_add_issue_with_attachment_to_specific_project
97 def test_add_issue_with_attachment_to_specific_project
98 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
98 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
99 assert issue.is_a?(Issue)
99 assert issue.is_a?(Issue)
100 assert !issue.new_record?
100 assert !issue.new_record?
101 issue.reload
101 issue.reload
102 assert_equal 'Ticket created by email with attachment', issue.subject
102 assert_equal 'Ticket created by email with attachment', issue.subject
103 assert_equal User.find_by_login('jsmith'), issue.author
103 assert_equal User.find_by_login('jsmith'), issue.author
104 assert_equal Project.find(2), issue.project
104 assert_equal Project.find(2), issue.project
105 assert_equal 'This is a new ticket with attachments', issue.description
105 assert_equal 'This is a new ticket with attachments', issue.description
106 # Attachment properties
106 # Attachment properties
107 assert_equal 1, issue.attachments.size
107 assert_equal 1, issue.attachments.size
108 assert_equal 'Paella.jpg', issue.attachments.first.filename
108 assert_equal 'Paella.jpg', issue.attachments.first.filename
109 assert_equal 'image/jpeg', issue.attachments.first.content_type
109 assert_equal 'image/jpeg', issue.attachments.first.content_type
110 assert_equal 10790, issue.attachments.first.filesize
110 assert_equal 10790, issue.attachments.first.filesize
111 end
111 end
112
112
113 def test_add_issue_with_custom_fields
113 def test_add_issue_with_custom_fields
114 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
114 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
115 assert issue.is_a?(Issue)
115 assert issue.is_a?(Issue)
116 assert !issue.new_record?
116 assert !issue.new_record?
117 issue.reload
117 issue.reload
118 assert_equal 'New ticket with custom field values', issue.subject
118 assert_equal 'New ticket with custom field values', issue.subject
119 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
119 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
120 assert !issue.description.match(/^searchable field:/i)
120 assert !issue.description.match(/^searchable field:/i)
121 end
121 end
122
122
123 def test_add_issue_with_cc
123 def test_add_issue_with_cc
124 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
124 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
125 assert issue.is_a?(Issue)
125 assert issue.is_a?(Issue)
126 assert !issue.new_record?
126 assert !issue.new_record?
127 issue.reload
127 issue.reload
128 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
128 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
129 assert_equal 1, issue.watchers.size
129 assert_equal 1, issue.watchers.size
130 end
130 end
131
131
132 def test_add_issue_without_from_header
132 def test_add_issue_without_from_header
133 Role.anonymous.add_permission!(:add_issues)
133 Role.anonymous.add_permission!(:add_issues)
134 assert_equal false, submit_email('ticket_without_from_header.eml')
134 assert_equal false, submit_email('ticket_without_from_header.eml')
135 end
135 end
136
136
137 def test_add_issue_should_send_email_notification
138 ActionMailer::Base.deliveries.clear
139 # This email contains: 'Project: onlinestore'
140 issue = submit_email('ticket_on_given_project.eml')
141 assert issue.is_a?(Issue)
142 assert_equal 1, ActionMailer::Base.deliveries.size
143 end
144
137 def test_add_issue_note
145 def test_add_issue_note
138 journal = submit_email('ticket_reply.eml')
146 journal = submit_email('ticket_reply.eml')
139 assert journal.is_a?(Journal)
147 assert journal.is_a?(Journal)
140 assert_equal User.find_by_login('jsmith'), journal.user
148 assert_equal User.find_by_login('jsmith'), journal.user
141 assert_equal Issue.find(2), journal.journalized
149 assert_equal Issue.find(2), journal.journalized
142 assert_match /This is reply/, journal.notes
150 assert_match /This is reply/, journal.notes
143 end
151 end
144
152
145 def test_add_issue_note_with_status_change
153 def test_add_issue_note_with_status_change
146 # This email contains: 'Status: Resolved'
154 # This email contains: 'Status: Resolved'
147 journal = submit_email('ticket_reply_with_status.eml')
155 journal = submit_email('ticket_reply_with_status.eml')
148 assert journal.is_a?(Journal)
156 assert journal.is_a?(Journal)
149 issue = Issue.find(journal.issue.id)
157 issue = Issue.find(journal.issue.id)
150 assert_equal User.find_by_login('jsmith'), journal.user
158 assert_equal User.find_by_login('jsmith'), journal.user
151 assert_equal Issue.find(2), journal.journalized
159 assert_equal Issue.find(2), journal.journalized
152 assert_match /This is reply/, journal.notes
160 assert_match /This is reply/, journal.notes
153 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
161 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
154 end
162 end
155
163
164 def test_add_issue_note_should_send_email_notification
165 ActionMailer::Base.deliveries.clear
166 journal = submit_email('ticket_reply.eml')
167 assert journal.is_a?(Journal)
168 assert_equal 1, ActionMailer::Base.deliveries.size
169 end
170
156 def test_reply_to_a_message
171 def test_reply_to_a_message
157 m = submit_email('message_reply.eml')
172 m = submit_email('message_reply.eml')
158 assert m.is_a?(Message)
173 assert m.is_a?(Message)
159 assert !m.new_record?
174 assert !m.new_record?
160 m.reload
175 m.reload
161 assert_equal 'Reply via email', m.subject
176 assert_equal 'Reply via email', m.subject
162 # The email replies to message #2 which is part of the thread of message #1
177 # The email replies to message #2 which is part of the thread of message #1
163 assert_equal Message.find(1), m.parent
178 assert_equal Message.find(1), m.parent
164 end
179 end
165
180
166 def test_reply_to_a_message_by_subject
181 def test_reply_to_a_message_by_subject
167 m = submit_email('message_reply_by_subject.eml')
182 m = submit_email('message_reply_by_subject.eml')
168 assert m.is_a?(Message)
183 assert m.is_a?(Message)
169 assert !m.new_record?
184 assert !m.new_record?
170 m.reload
185 m.reload
171 assert_equal 'Reply to the first post', m.subject
186 assert_equal 'Reply to the first post', m.subject
172 assert_equal Message.find(1), m.parent
187 assert_equal Message.find(1), m.parent
173 end
188 end
174
189
175 def test_should_strip_tags_of_html_only_emails
190 def test_should_strip_tags_of_html_only_emails
176 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
191 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
177 assert issue.is_a?(Issue)
192 assert issue.is_a?(Issue)
178 assert !issue.new_record?
193 assert !issue.new_record?
179 issue.reload
194 issue.reload
180 assert_equal 'HTML email', issue.subject
195 assert_equal 'HTML email', issue.subject
181 assert_equal 'This is a html-only email.', issue.description
196 assert_equal 'This is a html-only email.', issue.description
182 end
197 end
183
198
184 private
199 private
185
200
186 def submit_email(filename, options={})
201 def submit_email(filename, options={})
187 raw = IO.read(File.join(FIXTURES_PATH, filename))
202 raw = IO.read(File.join(FIXTURES_PATH, filename))
188 MailHandler.receive(raw, options)
203 MailHandler.receive(raw, options)
189 end
204 end
190 end
205 end
@@ -1,63 +1,77
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class NewsTest < Test::Unit::TestCase
20 class NewsTest < Test::Unit::TestCase
21 fixtures :projects, :users, :roles, :members, :enabled_modules, :news
21 fixtures :projects, :users, :roles, :members, :enabled_modules, :news
22
22
23 def valid_news
24 { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.find(:first) }
25 end
26
27
23 def setup
28 def setup
24 end
29 end
25
30
31 def test_create_should_send_email_notification
32 ActionMailer::Base.deliveries.clear
33 Setting.notified_events << 'news_added'
34 news = Project.find(:first).news.new(valid_news)
35
36 assert news.save
37 assert_equal 1, ActionMailer::Base.deliveries.size
38 end
39
26 def test_should_include_news_for_projects_with_news_enabled
40 def test_should_include_news_for_projects_with_news_enabled
27 project = projects(:projects_001)
41 project = projects(:projects_001)
28 assert project.enabled_modules.any?{ |em| em.name == 'news' }
42 assert project.enabled_modules.any?{ |em| em.name == 'news' }
29
43
30 # News.latest should return news from projects_001
44 # News.latest should return news from projects_001
31 assert News.latest.any? { |news| news.project == project }
45 assert News.latest.any? { |news| news.project == project }
32 end
46 end
33
47
34 def test_should_not_include_news_for_projects_with_news_disabled
48 def test_should_not_include_news_for_projects_with_news_disabled
35 # The projects_002 (OnlineStore) doesn't have the news module enabled, use that project for this test
49 # The projects_002 (OnlineStore) doesn't have the news module enabled, use that project for this test
36 project = projects(:projects_002)
50 project = projects(:projects_002)
37 assert ! project.enabled_modules.any?{ |em| em.name == 'news' }
51 assert ! project.enabled_modules.any?{ |em| em.name == 'news' }
38
52
39 # Add a piece of news to the project
53 # Add a piece of news to the project
40 news = project.news.create(:title => 'Test news', :description => 'This should not be returned by News.latest')
54 news = project.news.create(valid_news)
41
55
42 # News.latest should not return that new piece of news
56 # News.latest should not return that new piece of news
43 assert News.latest.include?(news) == false
57 assert News.latest.include?(news) == false
44 end
58 end
45
59
46 def test_should_only_include_news_from_projects_visibly_to_the_user
60 def test_should_only_include_news_from_projects_visibly_to_the_user
47 # users_001 has no memberships so can only get news from public project
61 # users_001 has no memberships so can only get news from public project
48 assert News.latest(users(:users_001)).all? { |news| news.project.is_public? }
62 assert News.latest(users(:users_001)).all? { |news| news.project.is_public? }
49 end
63 end
50
64
51 def test_should_limit_the_amount_of_returned_news
65 def test_should_limit_the_amount_of_returned_news
52 # Make sure we have a bunch of news stories
66 # Make sure we have a bunch of news stories
53 10.times { projects(:projects_001).news.create(:title => 'Test news', :description => 'Lorem ipsum etc') }
67 10.times { projects(:projects_001).news.create(valid_news) }
54 assert_equal 2, News.latest(users(:users_002), 2).size
68 assert_equal 2, News.latest(users(:users_002), 2).size
55 assert_equal 6, News.latest(users(:users_002), 6).size
69 assert_equal 6, News.latest(users(:users_002), 6).size
56 end
70 end
57
71
58 def test_should_return_5_news_stories_by_default
72 def test_should_return_5_news_stories_by_default
59 # Make sure we have a bunch of news stories
73 # Make sure we have a bunch of news stories
60 10.times { projects(:projects_001).news.create(:title => 'Test news', :description => 'Lorem ipsum etc') }
74 10.times { projects(:projects_001).news.create(valid_news) }
61 assert_equal 5, News.latest(users(:users_004)).size
75 assert_equal 5, News.latest(users(:users_004)).size
62 end
76 end
63 end
77 end
General Comments 0
You need to be logged in to leave comments. Login now