##// END OF EJS Templates
Backported r14796 (#21150)....
Jean-Philippe Lang -
r14458:4a254b6f0663
parent child
Show More
@@ -1,141 +1,146
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 TimeEntry < ActiveRecord::Base
18 class TimeEntry < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 # could have used polymorphic association
20 # could have used polymorphic association
21 # project association here allows easy loading of time entries at project level with one database trip
21 # project association here allows easy loading of time entries at project level with one database trip
22 belongs_to :project
22 belongs_to :project
23 belongs_to :issue
23 belongs_to :issue
24 belongs_to :user
24 belongs_to :user
25 belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
25 belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
26
26
27 attr_protected :user_id, :tyear, :tmonth, :tweek
27 attr_protected :user_id, :tyear, :tmonth, :tweek
28
28
29 acts_as_customizable
29 acts_as_customizable
30 acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
30 acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
31 :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
31 :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
32 :author => :user,
32 :author => :user,
33 :group => :issue,
33 :group => :issue,
34 :description => :comments
34 :description => :comments
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.created_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.created_on",
37 :author_key => :user_id,
37 :author_key => :user_id,
38 :find_options => {:include => :project}
38 :find_options => {:include => :project}
39
39
40 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
40 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
41 validates_numericality_of :hours, :allow_nil => true, :message => :invalid
41 validates_numericality_of :hours, :allow_nil => true, :message => :invalid
42 validates_length_of :comments, :maximum => 255, :allow_nil => true
42 validates_length_of :comments, :maximum => 255, :allow_nil => true
43 validates :spent_on, :date => true
43 validates :spent_on, :date => true
44 before_validation :set_project_if_nil
44 before_validation :set_project_if_nil
45 validate :validate_time_entry
45 validate :validate_time_entry
46
46
47 scope :visible, lambda {|*args|
47 scope :visible, lambda {|*args|
48 includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args))
48 includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args))
49 }
49 }
50 scope :on_issue, lambda {|issue|
50 scope :on_issue, lambda {|issue|
51 includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}")
51 includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}")
52 }
52 }
53 scope :on_project, lambda {|project, include_subprojects|
53 scope :on_project, lambda {|project, include_subprojects|
54 includes(:project).where(project.project_condition(include_subprojects))
54 includes(:project).where(project.project_condition(include_subprojects))
55 }
55 }
56 scope :spent_between, lambda {|from, to|
56 scope :spent_between, lambda {|from, to|
57 if from && to
57 if from && to
58 where("#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to)
58 where("#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to)
59 elsif from
59 elsif from
60 where("#{TimeEntry.table_name}.spent_on >= ?", from)
60 where("#{TimeEntry.table_name}.spent_on >= ?", from)
61 elsif to
61 elsif to
62 where("#{TimeEntry.table_name}.spent_on <= ?", to)
62 where("#{TimeEntry.table_name}.spent_on <= ?", to)
63 else
63 else
64 where(nil)
64 where(nil)
65 end
65 end
66 }
66 }
67
67
68 safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
68 safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
69
69
70 def initialize(attributes=nil, *args)
70 def initialize(attributes=nil, *args)
71 super
71 super
72 if new_record? && self.activity.nil?
72 if new_record? && self.activity.nil?
73 if default_activity = TimeEntryActivity.default
73 if default_activity = TimeEntryActivity.default
74 self.activity_id = default_activity.id
74 self.activity_id = default_activity.id
75 end
75 end
76 self.hours = nil if hours == 0
76 self.hours = nil if hours == 0
77 end
77 end
78 end
78 end
79
79
80 def safe_attributes=(attrs, user=User.current)
80 def safe_attributes=(attrs, user=User.current)
81 if attrs
81 if attrs
82 attrs = super(attrs)
82 attrs = super(attrs)
83 if issue_id_changed? && attrs[:project_id].blank? && issue && issue.project_id != project_id
83 if issue_id_changed? && issue
84 if user.allowed_to?(:log_time, issue.project)
84 if issue.visible?(user) && user.allowed_to?(:log_time, issue.project)
85 self.project_id = issue.project_id
85 if attrs[:project_id].blank? && issue.project_id != project_id
86 self.project_id = issue.project_id
87 end
88 @invalid_issue_id = nil
89 else
90 @invalid_issue_id = issue_id
86 end
91 end
87 end
92 end
88 end
93 end
89 attrs
94 attrs
90 end
95 end
91
96
92 def set_project_if_nil
97 def set_project_if_nil
93 self.project = issue.project if issue && project.nil?
98 self.project = issue.project if issue && project.nil?
94 end
99 end
95
100
96 def validate_time_entry
101 def validate_time_entry
97 errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
102 errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
98 errors.add :project_id, :invalid if project.nil?
103 errors.add :project_id, :invalid if project.nil?
99 errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
104 errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project) || @invalid_issue_id
100 end
105 end
101
106
102 def hours=(h)
107 def hours=(h)
103 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
108 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
104 end
109 end
105
110
106 def hours
111 def hours
107 h = read_attribute(:hours)
112 h = read_attribute(:hours)
108 if h.is_a?(Float)
113 if h.is_a?(Float)
109 h.round(2)
114 h.round(2)
110 else
115 else
111 h
116 h
112 end
117 end
113 end
118 end
114
119
115 # tyear, tmonth, tweek assigned where setting spent_on attributes
120 # tyear, tmonth, tweek assigned where setting spent_on attributes
116 # these attributes make time aggregations easier
121 # these attributes make time aggregations easier
117 def spent_on=(date)
122 def spent_on=(date)
118 super
123 super
119 if spent_on.is_a?(Time)
124 if spent_on.is_a?(Time)
120 self.spent_on = spent_on.to_date
125 self.spent_on = spent_on.to_date
121 end
126 end
122 self.tyear = spent_on ? spent_on.year : nil
127 self.tyear = spent_on ? spent_on.year : nil
123 self.tmonth = spent_on ? spent_on.month : nil
128 self.tmonth = spent_on ? spent_on.month : nil
124 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
129 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
125 end
130 end
126
131
127 # Returns true if the time entry can be edited by usr, otherwise false
132 # Returns true if the time entry can be edited by usr, otherwise false
128 def editable_by?(usr)
133 def editable_by?(usr)
129 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
134 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
130 end
135 end
131
136
132 # Returns the custom_field_values that can be edited by the given user
137 # Returns the custom_field_values that can be edited by the given user
133 def editable_custom_field_values(user=nil)
138 def editable_custom_field_values(user=nil)
134 visible_custom_field_values
139 visible_custom_field_values
135 end
140 end
136
141
137 # Returns the custom fields that can be edited by the given user
142 # Returns the custom fields that can be edited by the given user
138 def editable_custom_fields(user=nil)
143 def editable_custom_fields(user=nil)
139 editable_custom_field_values(user).map(&:custom_field).uniq
144 editable_custom_field_values(user).map(&:custom_field).uniq
140 end
145 end
141 end
146 end
@@ -1,34 +1,36
1 <%= error_messages_for 'time_entry' %>
1 <%= error_messages_for 'time_entry' %>
2 <%= back_url_hidden_field_tag %>
2 <%= back_url_hidden_field_tag %>
3
3
4 <div class="box tabular">
4 <div class="box tabular">
5 <% if @time_entry.new_record? %>
5 <% if @time_entry.new_record? %>
6 <% if params[:project_id] %>
6 <% if params[:project_id] %>
7 <%= hidden_field_tag 'project_id', params[:project_id] %>
7 <%= hidden_field_tag 'project_id', params[:project_id] %>
8 <% elsif params[:issue_id] %>
8 <% elsif params[:issue_id] %>
9 <%= hidden_field_tag 'issue_id', params[:issue_id] %>
9 <%= hidden_field_tag 'issue_id', params[:issue_id] %>
10 <% else %>
10 <% else %>
11 <p><%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project, :include_blank => true) %></p>
11 <p><%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project, :include_blank => true) %></p>
12 <% end %>
12 <% end %>
13 <% end %>
13 <% end %>
14 <p>
14 <p>
15 <%= f.text_field :issue_id, :size => 6 %>
15 <%= f.text_field :issue_id, :size => 6 %>
16 <span id="time_entry_issue"><%= "#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}" if @time_entry.issue.try(:visible?) %></span>
16 <% if @time_entry.issue.try(:visible?) %>
17 <span id="time_entry_issue"><%= "#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}" %></span>
18 <% end %>
17 </p>
19 </p>
18 <p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
20 <p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
19 <p><%= f.text_field :hours, :size => 6, :required => true %></p>
21 <p><%= f.text_field :hours, :size => 6, :required => true %></p>
20 <p><%= f.text_field :comments, :size => 100, :maxlength => 255 %></p>
22 <p><%= f.text_field :comments, :size => 100, :maxlength => 255 %></p>
21 <p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
23 <p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
22 <% @time_entry.custom_field_values.each do |value| %>
24 <% @time_entry.custom_field_values.each do |value| %>
23 <p><%= custom_field_tag_with_label :time_entry, value %></p>
25 <p><%= custom_field_tag_with_label :time_entry, value %></p>
24 <% end %>
26 <% end %>
25 <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
27 <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
26 </div>
28 </div>
27
29
28 <%= javascript_tag do %>
30 <%= javascript_tag do %>
29 observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', {
31 observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', {
30 select: function(event, ui) {
32 select: function(event, ui) {
31 $('#time_entry_issue').text(ui.item.label);
33 $('#time_entry_issue').text(ui.item.label);
32 }
34 }
33 });
35 });
34 <% end %>
36 <% end %>
@@ -1,756 +1,773
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Redmine - project management software
2 # Redmine - project management software
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require File.expand_path('../../test_helper', __FILE__)
19 require File.expand_path('../../test_helper', __FILE__)
20
20
21 class TimelogControllerTest < ActionController::TestCase
21 class TimelogControllerTest < ActionController::TestCase
22 fixtures :projects, :enabled_modules, :roles, :members,
22 fixtures :projects, :enabled_modules, :roles, :members,
23 :member_roles, :issues, :time_entries, :users,
23 :member_roles, :issues, :time_entries, :users,
24 :trackers, :enumerations, :issue_statuses,
24 :trackers, :enumerations, :issue_statuses,
25 :custom_fields, :custom_values,
25 :custom_fields, :custom_values,
26 :projects_trackers, :custom_fields_trackers,
26 :projects_trackers, :custom_fields_trackers,
27 :custom_fields_projects
27 :custom_fields_projects
28
28
29 include Redmine::I18n
29 include Redmine::I18n
30
30
31 def test_new
31 def test_new
32 @request.session[:user_id] = 3
32 @request.session[:user_id] = 3
33 get :new
33 get :new
34 assert_response :success
34 assert_response :success
35 assert_template 'new'
35 assert_template 'new'
36 assert_select 'input[name=?][type=hidden]', 'project_id', 0
36 assert_select 'input[name=?][type=hidden]', 'project_id', 0
37 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
37 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
38 assert_select 'select[name=?]', 'time_entry[project_id]' do
38 assert_select 'select[name=?]', 'time_entry[project_id]' do
39 # blank option for project
39 # blank option for project
40 assert_select 'option[value=]'
40 assert_select 'option[value=]'
41 end
41 end
42 end
42 end
43
43
44 def test_new_with_project_id
44 def test_new_with_project_id
45 @request.session[:user_id] = 3
45 @request.session[:user_id] = 3
46 get :new, :project_id => 1
46 get :new, :project_id => 1
47 assert_response :success
47 assert_response :success
48 assert_template 'new'
48 assert_template 'new'
49 assert_select 'input[name=?][type=hidden]', 'project_id'
49 assert_select 'input[name=?][type=hidden]', 'project_id'
50 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
50 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
51 assert_select 'select[name=?]', 'time_entry[project_id]', 0
51 assert_select 'select[name=?]', 'time_entry[project_id]', 0
52 end
52 end
53
53
54 def test_new_with_issue_id
54 def test_new_with_issue_id
55 @request.session[:user_id] = 3
55 @request.session[:user_id] = 3
56 get :new, :issue_id => 2
56 get :new, :issue_id => 2
57 assert_response :success
57 assert_response :success
58 assert_template 'new'
58 assert_template 'new'
59 assert_select 'input[name=?][type=hidden]', 'project_id', 0
59 assert_select 'input[name=?][type=hidden]', 'project_id', 0
60 assert_select 'input[name=?][type=hidden]', 'issue_id'
60 assert_select 'input[name=?][type=hidden]', 'issue_id'
61 assert_select 'select[name=?]', 'time_entry[project_id]', 0
61 assert_select 'select[name=?]', 'time_entry[project_id]', 0
62 end
62 end
63
63
64 def test_new_without_project_should_prefill_the_form
64 def test_new_without_project_should_prefill_the_form
65 @request.session[:user_id] = 3
65 @request.session[:user_id] = 3
66 get :new, :time_entry => {:project_id => '1'}
66 get :new, :time_entry => {:project_id => '1'}
67 assert_response :success
67 assert_response :success
68 assert_template 'new'
68 assert_template 'new'
69 assert_select 'select[name=?]', 'time_entry[project_id]' do
69 assert_select 'select[name=?]', 'time_entry[project_id]' do
70 assert_select 'option[value=1][selected=selected]'
70 assert_select 'option[value=1][selected=selected]'
71 end
71 end
72 end
72 end
73
73
74 def test_new_without_project_should_deny_without_permission
74 def test_new_without_project_should_deny_without_permission
75 Role.all.each {|role| role.remove_permission! :log_time}
75 Role.all.each {|role| role.remove_permission! :log_time}
76 @request.session[:user_id] = 3
76 @request.session[:user_id] = 3
77
77
78 get :new
78 get :new
79 assert_response 403
79 assert_response 403
80 end
80 end
81
81
82 def test_new_should_select_default_activity
82 def test_new_should_select_default_activity
83 @request.session[:user_id] = 3
83 @request.session[:user_id] = 3
84 get :new, :project_id => 1
84 get :new, :project_id => 1
85 assert_response :success
85 assert_response :success
86 assert_select 'select[name=?]', 'time_entry[activity_id]' do
86 assert_select 'select[name=?]', 'time_entry[activity_id]' do
87 assert_select 'option[selected=selected]', :text => 'Development'
87 assert_select 'option[selected=selected]', :text => 'Development'
88 end
88 end
89 end
89 end
90
90
91 def test_new_should_only_show_active_time_entry_activities
91 def test_new_should_only_show_active_time_entry_activities
92 @request.session[:user_id] = 3
92 @request.session[:user_id] = 3
93 get :new, :project_id => 1
93 get :new, :project_id => 1
94 assert_response :success
94 assert_response :success
95 assert_no_tag 'option', :content => 'Inactive Activity'
95 assert_no_tag 'option', :content => 'Inactive Activity'
96 end
96 end
97
97
98 def test_get_edit_existing_time
98 def test_get_edit_existing_time
99 @request.session[:user_id] = 2
99 @request.session[:user_id] = 2
100 get :edit, :id => 2, :project_id => nil
100 get :edit, :id => 2, :project_id => nil
101 assert_response :success
101 assert_response :success
102 assert_template 'edit'
102 assert_template 'edit'
103 # Default activity selected
103 # Default activity selected
104 assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/time_entries/2' }
104 assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/time_entries/2' }
105 end
105 end
106
106
107 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
107 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
108 te = TimeEntry.find(1)
108 te = TimeEntry.find(1)
109 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
109 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
110 te.save!
110 te.save!
111
111
112 @request.session[:user_id] = 1
112 @request.session[:user_id] = 1
113 get :edit, :project_id => 1, :id => 1
113 get :edit, :project_id => 1, :id => 1
114 assert_response :success
114 assert_response :success
115 assert_template 'edit'
115 assert_template 'edit'
116 # Blank option since nothing is pre-selected
116 # Blank option since nothing is pre-selected
117 assert_tag :tag => 'option', :content => '--- Please select ---'
117 assert_tag :tag => 'option', :content => '--- Please select ---'
118 end
118 end
119
119
120 def test_post_create
120 def test_post_create
121 @request.session[:user_id] = 3
121 @request.session[:user_id] = 3
122 assert_difference 'TimeEntry.count' do
122 assert_difference 'TimeEntry.count' do
123 post :create, :project_id => 1,
123 post :create, :project_id => 1,
124 :time_entry => {:comments => 'Some work on TimelogControllerTest',
124 :time_entry => {:comments => 'Some work on TimelogControllerTest',
125 # Not the default activity
125 # Not the default activity
126 :activity_id => '11',
126 :activity_id => '11',
127 :spent_on => '2008-03-14',
127 :spent_on => '2008-03-14',
128 :issue_id => '1',
128 :issue_id => '1',
129 :hours => '7.3'}
129 :hours => '7.3'}
130 assert_redirected_to '/projects/ecookbook/time_entries'
130 assert_redirected_to '/projects/ecookbook/time_entries'
131 end
131 end
132
132
133 t = TimeEntry.order('id DESC').first
133 t = TimeEntry.order('id DESC').first
134 assert_not_nil t
134 assert_not_nil t
135 assert_equal 'Some work on TimelogControllerTest', t.comments
135 assert_equal 'Some work on TimelogControllerTest', t.comments
136 assert_equal 1, t.project_id
136 assert_equal 1, t.project_id
137 assert_equal 1, t.issue_id
137 assert_equal 1, t.issue_id
138 assert_equal 11, t.activity_id
138 assert_equal 11, t.activity_id
139 assert_equal 7.3, t.hours
139 assert_equal 7.3, t.hours
140 assert_equal 3, t.user_id
140 assert_equal 3, t.user_id
141 end
141 end
142
142
143 def test_post_create_with_blank_issue
143 def test_post_create_with_blank_issue
144 @request.session[:user_id] = 3
144 @request.session[:user_id] = 3
145 assert_difference 'TimeEntry.count' do
145 assert_difference 'TimeEntry.count' do
146 post :create, :project_id => 1,
146 post :create, :project_id => 1,
147 :time_entry => {:comments => 'Some work on TimelogControllerTest',
147 :time_entry => {:comments => 'Some work on TimelogControllerTest',
148 # Not the default activity
148 # Not the default activity
149 :activity_id => '11',
149 :activity_id => '11',
150 :issue_id => '',
150 :issue_id => '',
151 :spent_on => '2008-03-14',
151 :spent_on => '2008-03-14',
152 :hours => '7.3'}
152 :hours => '7.3'}
153 assert_redirected_to '/projects/ecookbook/time_entries'
153 assert_redirected_to '/projects/ecookbook/time_entries'
154 end
154 end
155
155
156 t = TimeEntry.order('id DESC').first
156 t = TimeEntry.order('id DESC').first
157 assert_not_nil t
157 assert_not_nil t
158 assert_equal 'Some work on TimelogControllerTest', t.comments
158 assert_equal 'Some work on TimelogControllerTest', t.comments
159 assert_equal 1, t.project_id
159 assert_equal 1, t.project_id
160 assert_nil t.issue_id
160 assert_nil t.issue_id
161 assert_equal 11, t.activity_id
161 assert_equal 11, t.activity_id
162 assert_equal 7.3, t.hours
162 assert_equal 7.3, t.hours
163 assert_equal 3, t.user_id
163 assert_equal 3, t.user_id
164 end
164 end
165
165
166 def test_create_on_issue_that_is_not_visible_should_not_disclose_subject
167 issue = Issue.generate!(:subject => "issue_that_is_not_visible", :is_private => true)
168 assert !issue.visible?(User.find(3))
169
170 @request.session[:user_id] = 3
171 assert_no_difference 'TimeEntry.count' do
172 post :create, :time_entry => {
173 :project_id => '', :issue_id => issue.id.to_s,
174 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
175 }
176 end
177 assert_error_tag :content => /Issue is invalid/
178 assert_select "input[name=?][value=?]", "time_entry[issue_id]", issue.id.to_s
179 assert_select "#time_entry_issue", 0
180 assert !response.body.include?('issue_that_is_not_visible')
181 end
182
166 def test_create_and_continue_at_project_level
183 def test_create_and_continue_at_project_level
167 @request.session[:user_id] = 2
184 @request.session[:user_id] = 2
168 assert_difference 'TimeEntry.count' do
185 assert_difference 'TimeEntry.count' do
169 post :create, :time_entry => {:project_id => '1',
186 post :create, :time_entry => {:project_id => '1',
170 :activity_id => '11',
187 :activity_id => '11',
171 :issue_id => '',
188 :issue_id => '',
172 :spent_on => '2008-03-14',
189 :spent_on => '2008-03-14',
173 :hours => '7.3'},
190 :hours => '7.3'},
174 :continue => '1'
191 :continue => '1'
175 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
192 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
176 end
193 end
177 end
194 end
178
195
179 def test_create_and_continue_at_issue_level
196 def test_create_and_continue_at_issue_level
180 @request.session[:user_id] = 2
197 @request.session[:user_id] = 2
181 assert_difference 'TimeEntry.count' do
198 assert_difference 'TimeEntry.count' do
182 post :create, :time_entry => {:project_id => '',
199 post :create, :time_entry => {:project_id => '',
183 :activity_id => '11',
200 :activity_id => '11',
184 :issue_id => '1',
201 :issue_id => '1',
185 :spent_on => '2008-03-14',
202 :spent_on => '2008-03-14',
186 :hours => '7.3'},
203 :hours => '7.3'},
187 :continue => '1'
204 :continue => '1'
188 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
205 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
189 end
206 end
190 end
207 end
191
208
192 def test_create_and_continue_with_project_id
209 def test_create_and_continue_with_project_id
193 @request.session[:user_id] = 2
210 @request.session[:user_id] = 2
194 assert_difference 'TimeEntry.count' do
211 assert_difference 'TimeEntry.count' do
195 post :create, :project_id => 1,
212 post :create, :project_id => 1,
196 :time_entry => {:activity_id => '11',
213 :time_entry => {:activity_id => '11',
197 :issue_id => '',
214 :issue_id => '',
198 :spent_on => '2008-03-14',
215 :spent_on => '2008-03-14',
199 :hours => '7.3'},
216 :hours => '7.3'},
200 :continue => '1'
217 :continue => '1'
201 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D='
218 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D='
202 end
219 end
203 end
220 end
204
221
205 def test_create_and_continue_with_issue_id
222 def test_create_and_continue_with_issue_id
206 @request.session[:user_id] = 2
223 @request.session[:user_id] = 2
207 assert_difference 'TimeEntry.count' do
224 assert_difference 'TimeEntry.count' do
208 post :create, :issue_id => 1,
225 post :create, :issue_id => 1,
209 :time_entry => {:activity_id => '11',
226 :time_entry => {:activity_id => '11',
210 :issue_id => '1',
227 :issue_id => '1',
211 :spent_on => '2008-03-14',
228 :spent_on => '2008-03-14',
212 :hours => '7.3'},
229 :hours => '7.3'},
213 :continue => '1'
230 :continue => '1'
214 assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
231 assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
215 end
232 end
216 end
233 end
217
234
218 def test_create_without_log_time_permission_should_be_denied
235 def test_create_without_log_time_permission_should_be_denied
219 @request.session[:user_id] = 2
236 @request.session[:user_id] = 2
220 Role.find_by_name('Manager').remove_permission! :log_time
237 Role.find_by_name('Manager').remove_permission! :log_time
221 post :create, :project_id => 1,
238 post :create, :project_id => 1,
222 :time_entry => {:activity_id => '11',
239 :time_entry => {:activity_id => '11',
223 :issue_id => '',
240 :issue_id => '',
224 :spent_on => '2008-03-14',
241 :spent_on => '2008-03-14',
225 :hours => '7.3'}
242 :hours => '7.3'}
226
243
227 assert_response 403
244 assert_response 403
228 end
245 end
229
246
230 def test_create_without_project_and_issue_should_fail
247 def test_create_without_project_and_issue_should_fail
231 @request.session[:user_id] = 2
248 @request.session[:user_id] = 2
232 post :create, :time_entry => {:issue_id => ''}
249 post :create, :time_entry => {:issue_id => ''}
233
250
234 assert_response :success
251 assert_response :success
235 assert_template 'new'
252 assert_template 'new'
236 end
253 end
237
254
238 def test_create_with_failure
255 def test_create_with_failure
239 @request.session[:user_id] = 2
256 @request.session[:user_id] = 2
240 post :create, :project_id => 1,
257 post :create, :project_id => 1,
241 :time_entry => {:activity_id => '',
258 :time_entry => {:activity_id => '',
242 :issue_id => '',
259 :issue_id => '',
243 :spent_on => '2008-03-14',
260 :spent_on => '2008-03-14',
244 :hours => '7.3'}
261 :hours => '7.3'}
245
262
246 assert_response :success
263 assert_response :success
247 assert_template 'new'
264 assert_template 'new'
248 end
265 end
249
266
250 def test_create_without_project
267 def test_create_without_project
251 @request.session[:user_id] = 2
268 @request.session[:user_id] = 2
252 assert_difference 'TimeEntry.count' do
269 assert_difference 'TimeEntry.count' do
253 post :create, :time_entry => {:project_id => '1',
270 post :create, :time_entry => {:project_id => '1',
254 :activity_id => '11',
271 :activity_id => '11',
255 :issue_id => '',
272 :issue_id => '',
256 :spent_on => '2008-03-14',
273 :spent_on => '2008-03-14',
257 :hours => '7.3'}
274 :hours => '7.3'}
258 end
275 end
259
276
260 assert_redirected_to '/projects/ecookbook/time_entries'
277 assert_redirected_to '/projects/ecookbook/time_entries'
261 time_entry = TimeEntry.order('id DESC').first
278 time_entry = TimeEntry.order('id DESC').first
262 assert_equal 1, time_entry.project_id
279 assert_equal 1, time_entry.project_id
263 end
280 end
264
281
265 def test_create_without_project_should_fail_with_issue_not_inside_project
282 def test_create_without_project_should_fail_with_issue_not_inside_project
266 @request.session[:user_id] = 2
283 @request.session[:user_id] = 2
267 assert_no_difference 'TimeEntry.count' do
284 assert_no_difference 'TimeEntry.count' do
268 post :create, :time_entry => {:project_id => '1',
285 post :create, :time_entry => {:project_id => '1',
269 :activity_id => '11',
286 :activity_id => '11',
270 :issue_id => '5',
287 :issue_id => '5',
271 :spent_on => '2008-03-14',
288 :spent_on => '2008-03-14',
272 :hours => '7.3'}
289 :hours => '7.3'}
273 end
290 end
274
291
275 assert_response :success
292 assert_response :success
276 assert assigns(:time_entry).errors[:issue_id].present?
293 assert assigns(:time_entry).errors[:issue_id].present?
277 end
294 end
278
295
279 def test_create_without_project_should_deny_without_permission
296 def test_create_without_project_should_deny_without_permission
280 @request.session[:user_id] = 2
297 @request.session[:user_id] = 2
281 Project.find(3).disable_module!(:time_tracking)
298 Project.find(3).disable_module!(:time_tracking)
282
299
283 assert_no_difference 'TimeEntry.count' do
300 assert_no_difference 'TimeEntry.count' do
284 post :create, :time_entry => {:project_id => '3',
301 post :create, :time_entry => {:project_id => '3',
285 :activity_id => '11',
302 :activity_id => '11',
286 :issue_id => '',
303 :issue_id => '',
287 :spent_on => '2008-03-14',
304 :spent_on => '2008-03-14',
288 :hours => '7.3'}
305 :hours => '7.3'}
289 end
306 end
290
307
291 assert_response 403
308 assert_response 403
292 end
309 end
293
310
294 def test_create_without_project_with_failure
311 def test_create_without_project_with_failure
295 @request.session[:user_id] = 2
312 @request.session[:user_id] = 2
296 assert_no_difference 'TimeEntry.count' do
313 assert_no_difference 'TimeEntry.count' do
297 post :create, :time_entry => {:project_id => '1',
314 post :create, :time_entry => {:project_id => '1',
298 :activity_id => '11',
315 :activity_id => '11',
299 :issue_id => '',
316 :issue_id => '',
300 :spent_on => '2008-03-14',
317 :spent_on => '2008-03-14',
301 :hours => ''}
318 :hours => ''}
302 end
319 end
303
320
304 assert_response :success
321 assert_response :success
305 assert_tag 'select', :attributes => {:name => 'time_entry[project_id]'},
322 assert_tag 'select', :attributes => {:name => 'time_entry[project_id]'},
306 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
323 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
307 end
324 end
308
325
309 def test_update
326 def test_update
310 entry = TimeEntry.find(1)
327 entry = TimeEntry.find(1)
311 assert_equal 1, entry.issue_id
328 assert_equal 1, entry.issue_id
312 assert_equal 2, entry.user_id
329 assert_equal 2, entry.user_id
313
330
314 @request.session[:user_id] = 1
331 @request.session[:user_id] = 1
315 put :update, :id => 1,
332 put :update, :id => 1,
316 :time_entry => {:issue_id => '2',
333 :time_entry => {:issue_id => '2',
317 :hours => '8'}
334 :hours => '8'}
318 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
335 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
319 entry.reload
336 entry.reload
320
337
321 assert_equal 8, entry.hours
338 assert_equal 8, entry.hours
322 assert_equal 2, entry.issue_id
339 assert_equal 2, entry.issue_id
323 assert_equal 2, entry.user_id
340 assert_equal 2, entry.user_id
324 end
341 end
325
342
326 def test_update_should_allow_to_change_issue_to_another_project
343 def test_update_should_allow_to_change_issue_to_another_project
327 entry = TimeEntry.generate!(:issue_id => 1)
344 entry = TimeEntry.generate!(:issue_id => 1)
328
345
329 @request.session[:user_id] = 1
346 @request.session[:user_id] = 1
330 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
347 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
331 assert_response 302
348 assert_response 302
332 entry.reload
349 entry.reload
333
350
334 assert_equal 5, entry.issue_id
351 assert_equal 5, entry.issue_id
335 assert_equal 3, entry.project_id
352 assert_equal 3, entry.project_id
336 end
353 end
337
354
338 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
355 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
339 entry = TimeEntry.generate!(:issue_id => 1)
356 entry = TimeEntry.generate!(:issue_id => 1)
340 Project.find(3).disable_module!(:time_tracking)
357 Project.find(3).disable_module!(:time_tracking)
341
358
342 @request.session[:user_id] = 1
359 @request.session[:user_id] = 1
343 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
360 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
344 assert_response 200
361 assert_response 200
345 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
362 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
346 end
363 end
347
364
348 def test_get_bulk_edit
365 def test_get_bulk_edit
349 @request.session[:user_id] = 2
366 @request.session[:user_id] = 2
350 get :bulk_edit, :ids => [1, 2]
367 get :bulk_edit, :ids => [1, 2]
351 assert_response :success
368 assert_response :success
352 assert_template 'bulk_edit'
369 assert_template 'bulk_edit'
353
370
354 assert_select 'ul#bulk-selection' do
371 assert_select 'ul#bulk-selection' do
355 assert_select 'li', 2
372 assert_select 'li', 2
356 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
373 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
357 end
374 end
358
375
359 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
376 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
360 # System wide custom field
377 # System wide custom field
361 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
378 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
362
379
363 # Activities
380 # Activities
364 assert_select 'select[name=?]', 'time_entry[activity_id]' do
381 assert_select 'select[name=?]', 'time_entry[activity_id]' do
365 assert_select 'option[value=]', :text => '(No change)'
382 assert_select 'option[value=]', :text => '(No change)'
366 assert_select 'option[value=9]', :text => 'Design'
383 assert_select 'option[value=9]', :text => 'Design'
367 end
384 end
368 end
385 end
369 end
386 end
370
387
371 def test_get_bulk_edit_on_different_projects
388 def test_get_bulk_edit_on_different_projects
372 @request.session[:user_id] = 2
389 @request.session[:user_id] = 2
373 get :bulk_edit, :ids => [1, 2, 6]
390 get :bulk_edit, :ids => [1, 2, 6]
374 assert_response :success
391 assert_response :success
375 assert_template 'bulk_edit'
392 assert_template 'bulk_edit'
376 end
393 end
377
394
378 def test_bulk_edit_with_edit_own_time_entries_permission
395 def test_bulk_edit_with_edit_own_time_entries_permission
379 @request.session[:user_id] = 2
396 @request.session[:user_id] = 2
380 Role.find_by_name('Manager').remove_permission! :edit_time_entries
397 Role.find_by_name('Manager').remove_permission! :edit_time_entries
381 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
398 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
382 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
399 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
383
400
384 get :bulk_edit, :ids => ids
401 get :bulk_edit, :ids => ids
385 assert_response :success
402 assert_response :success
386 end
403 end
387
404
388 def test_bulk_update
405 def test_bulk_update
389 @request.session[:user_id] = 2
406 @request.session[:user_id] = 2
390 # update time entry activity
407 # update time entry activity
391 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
408 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
392
409
393 assert_response 302
410 assert_response 302
394 # check that the issues were updated
411 # check that the issues were updated
395 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
412 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
396 end
413 end
397
414
398 def test_bulk_update_with_failure
415 def test_bulk_update_with_failure
399 @request.session[:user_id] = 2
416 @request.session[:user_id] = 2
400 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
417 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
401
418
402 assert_response 302
419 assert_response 302
403 assert_match /Failed to save 2 time entrie/, flash[:error]
420 assert_match /Failed to save 2 time entrie/, flash[:error]
404 end
421 end
405
422
406 def test_bulk_update_on_different_projects
423 def test_bulk_update_on_different_projects
407 @request.session[:user_id] = 2
424 @request.session[:user_id] = 2
408 # makes user a manager on the other project
425 # makes user a manager on the other project
409 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
426 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
410
427
411 # update time entry activity
428 # update time entry activity
412 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
429 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
413
430
414 assert_response 302
431 assert_response 302
415 # check that the issues were updated
432 # check that the issues were updated
416 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
433 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
417 end
434 end
418
435
419 def test_bulk_update_on_different_projects_without_rights
436 def test_bulk_update_on_different_projects_without_rights
420 @request.session[:user_id] = 3
437 @request.session[:user_id] = 3
421 user = User.find(3)
438 user = User.find(3)
422 action = { :controller => "timelog", :action => "bulk_update" }
439 action = { :controller => "timelog", :action => "bulk_update" }
423 assert user.allowed_to?(action, TimeEntry.find(1).project)
440 assert user.allowed_to?(action, TimeEntry.find(1).project)
424 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
441 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
425 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
442 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
426 assert_response 403
443 assert_response 403
427 end
444 end
428
445
429 def test_bulk_update_with_edit_own_time_entries_permission
446 def test_bulk_update_with_edit_own_time_entries_permission
430 @request.session[:user_id] = 2
447 @request.session[:user_id] = 2
431 Role.find_by_name('Manager').remove_permission! :edit_time_entries
448 Role.find_by_name('Manager').remove_permission! :edit_time_entries
432 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
449 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
433 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
450 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
434
451
435 post :bulk_update, :ids => ids, :time_entry => { :activity_id => 9 }
452 post :bulk_update, :ids => ids, :time_entry => { :activity_id => 9 }
436 assert_response 302
453 assert_response 302
437 end
454 end
438
455
439 def test_bulk_update_with_edit_own_time_entries_permissions_should_be_denied_for_time_entries_of_other_user
456 def test_bulk_update_with_edit_own_time_entries_permissions_should_be_denied_for_time_entries_of_other_user
440 @request.session[:user_id] = 2
457 @request.session[:user_id] = 2
441 Role.find_by_name('Manager').remove_permission! :edit_time_entries
458 Role.find_by_name('Manager').remove_permission! :edit_time_entries
442 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
459 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
443
460
444 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9 }
461 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9 }
445 assert_response 403
462 assert_response 403
446 end
463 end
447
464
448 def test_bulk_update_custom_field
465 def test_bulk_update_custom_field
449 @request.session[:user_id] = 2
466 @request.session[:user_id] = 2
450 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
467 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
451
468
452 assert_response 302
469 assert_response 302
453 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
470 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
454 end
471 end
455
472
456 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
473 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
457 @request.session[:user_id] = 2
474 @request.session[:user_id] = 2
458 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
475 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
459
476
460 assert_response :redirect
477 assert_response :redirect
461 assert_redirected_to '/time_entries'
478 assert_redirected_to '/time_entries'
462 end
479 end
463
480
464 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
481 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
465 @request.session[:user_id] = 2
482 @request.session[:user_id] = 2
466 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
483 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
467
484
468 assert_response :redirect
485 assert_response :redirect
469 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
486 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
470 end
487 end
471
488
472 def test_post_bulk_update_without_edit_permission_should_be_denied
489 def test_post_bulk_update_without_edit_permission_should_be_denied
473 @request.session[:user_id] = 2
490 @request.session[:user_id] = 2
474 Role.find_by_name('Manager').remove_permission! :edit_time_entries
491 Role.find_by_name('Manager').remove_permission! :edit_time_entries
475 post :bulk_update, :ids => [1,2]
492 post :bulk_update, :ids => [1,2]
476
493
477 assert_response 403
494 assert_response 403
478 end
495 end
479
496
480 def test_destroy
497 def test_destroy
481 @request.session[:user_id] = 2
498 @request.session[:user_id] = 2
482 delete :destroy, :id => 1
499 delete :destroy, :id => 1
483 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
500 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
484 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
501 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
485 assert_nil TimeEntry.find_by_id(1)
502 assert_nil TimeEntry.find_by_id(1)
486 end
503 end
487
504
488 def test_destroy_should_fail
505 def test_destroy_should_fail
489 # simulate that this fails (e.g. due to a plugin), see #5700
506 # simulate that this fails (e.g. due to a plugin), see #5700
490 TimeEntry.any_instance.expects(:destroy).returns(false)
507 TimeEntry.any_instance.expects(:destroy).returns(false)
491
508
492 @request.session[:user_id] = 2
509 @request.session[:user_id] = 2
493 delete :destroy, :id => 1
510 delete :destroy, :id => 1
494 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
511 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
495 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
512 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
496 assert_not_nil TimeEntry.find_by_id(1)
513 assert_not_nil TimeEntry.find_by_id(1)
497 end
514 end
498
515
499 def test_index_all_projects
516 def test_index_all_projects
500 get :index
517 get :index
501 assert_response :success
518 assert_response :success
502 assert_template 'index'
519 assert_template 'index'
503 assert_not_nil assigns(:total_hours)
520 assert_not_nil assigns(:total_hours)
504 assert_equal "162.90", "%.2f" % assigns(:total_hours)
521 assert_equal "162.90", "%.2f" % assigns(:total_hours)
505 assert_tag :form,
522 assert_tag :form,
506 :attributes => {:action => "/time_entries", :id => 'query_form'}
523 :attributes => {:action => "/time_entries", :id => 'query_form'}
507 end
524 end
508
525
509 def test_index_all_projects_should_show_log_time_link
526 def test_index_all_projects_should_show_log_time_link
510 @request.session[:user_id] = 2
527 @request.session[:user_id] = 2
511 get :index
528 get :index
512 assert_response :success
529 assert_response :success
513 assert_template 'index'
530 assert_template 'index'
514 assert_tag 'a', :attributes => {:href => '/time_entries/new'}, :content => /Log time/
531 assert_tag 'a', :attributes => {:href => '/time_entries/new'}, :content => /Log time/
515 end
532 end
516
533
517 def test_index_my_spent_time
534 def test_index_my_spent_time
518 @request.session[:user_id] = 2
535 @request.session[:user_id] = 2
519 get :index, :user_id => 'me'
536 get :index, :user_id => 'me'
520 assert_response :success
537 assert_response :success
521 assert_template 'index'
538 assert_template 'index'
522 assert assigns(:entries).all? {|entry| entry.user_id == 2}
539 assert assigns(:entries).all? {|entry| entry.user_id == 2}
523 end
540 end
524
541
525 def test_index_at_project_level
542 def test_index_at_project_level
526 get :index, :project_id => 'ecookbook'
543 get :index, :project_id => 'ecookbook'
527 assert_response :success
544 assert_response :success
528 assert_template 'index'
545 assert_template 'index'
529 assert_not_nil assigns(:entries)
546 assert_not_nil assigns(:entries)
530 assert_equal 4, assigns(:entries).size
547 assert_equal 4, assigns(:entries).size
531 # project and subproject
548 # project and subproject
532 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
549 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
533 assert_not_nil assigns(:total_hours)
550 assert_not_nil assigns(:total_hours)
534 assert_equal "162.90", "%.2f" % assigns(:total_hours)
551 assert_equal "162.90", "%.2f" % assigns(:total_hours)
535 assert_tag :form,
552 assert_tag :form,
536 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
553 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
537 end
554 end
538
555
539 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
556 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
540 entry = TimeEntry.generate!(:project => Project.find(3))
557 entry = TimeEntry.generate!(:project => Project.find(3))
541
558
542 with_settings :display_subprojects_issues => '0' do
559 with_settings :display_subprojects_issues => '0' do
543 get :index, :project_id => 'ecookbook'
560 get :index, :project_id => 'ecookbook'
544 assert_response :success
561 assert_response :success
545 assert_template 'index'
562 assert_template 'index'
546 assert_not_include entry, assigns(:entries)
563 assert_not_include entry, assigns(:entries)
547 end
564 end
548 end
565 end
549
566
550 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
567 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
551 entry = TimeEntry.generate!(:project => Project.find(3))
568 entry = TimeEntry.generate!(:project => Project.find(3))
552
569
553 with_settings :display_subprojects_issues => '0' do
570 with_settings :display_subprojects_issues => '0' do
554 get :index, :project_id => 'ecookbook', :subproject_id => 3
571 get :index, :project_id => 'ecookbook', :subproject_id => 3
555 assert_response :success
572 assert_response :success
556 assert_template 'index'
573 assert_template 'index'
557 assert_include entry, assigns(:entries)
574 assert_include entry, assigns(:entries)
558 end
575 end
559 end
576 end
560
577
561 def test_index_at_project_level_with_date_range
578 def test_index_at_project_level_with_date_range
562 get :index, :project_id => 'ecookbook',
579 get :index, :project_id => 'ecookbook',
563 :f => ['spent_on'],
580 :f => ['spent_on'],
564 :op => {'spent_on' => '><'},
581 :op => {'spent_on' => '><'},
565 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
582 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
566 assert_response :success
583 assert_response :success
567 assert_template 'index'
584 assert_template 'index'
568 assert_not_nil assigns(:entries)
585 assert_not_nil assigns(:entries)
569 assert_equal 3, assigns(:entries).size
586 assert_equal 3, assigns(:entries).size
570 assert_not_nil assigns(:total_hours)
587 assert_not_nil assigns(:total_hours)
571 assert_equal "12.90", "%.2f" % assigns(:total_hours)
588 assert_equal "12.90", "%.2f" % assigns(:total_hours)
572 assert_tag :form,
589 assert_tag :form,
573 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
590 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
574 end
591 end
575
592
576 def test_index_at_project_level_with_date_range_using_from_and_to_params
593 def test_index_at_project_level_with_date_range_using_from_and_to_params
577 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
594 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
578 assert_response :success
595 assert_response :success
579 assert_template 'index'
596 assert_template 'index'
580 assert_not_nil assigns(:entries)
597 assert_not_nil assigns(:entries)
581 assert_equal 3, assigns(:entries).size
598 assert_equal 3, assigns(:entries).size
582 assert_not_nil assigns(:total_hours)
599 assert_not_nil assigns(:total_hours)
583 assert_equal "12.90", "%.2f" % assigns(:total_hours)
600 assert_equal "12.90", "%.2f" % assigns(:total_hours)
584 assert_tag :form,
601 assert_tag :form,
585 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
602 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
586 end
603 end
587
604
588 def test_index_at_project_level_with_period
605 def test_index_at_project_level_with_period
589 get :index, :project_id => 'ecookbook',
606 get :index, :project_id => 'ecookbook',
590 :f => ['spent_on'],
607 :f => ['spent_on'],
591 :op => {'spent_on' => '>t-'},
608 :op => {'spent_on' => '>t-'},
592 :v => {'spent_on' => ['7']}
609 :v => {'spent_on' => ['7']}
593 assert_response :success
610 assert_response :success
594 assert_template 'index'
611 assert_template 'index'
595 assert_not_nil assigns(:entries)
612 assert_not_nil assigns(:entries)
596 assert_not_nil assigns(:total_hours)
613 assert_not_nil assigns(:total_hours)
597 assert_tag :form,
614 assert_tag :form,
598 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
615 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
599 end
616 end
600
617
601 def test_index_at_issue_level
618 def test_index_at_issue_level
602 get :index, :issue_id => 1
619 get :index, :issue_id => 1
603 assert_response :success
620 assert_response :success
604 assert_template 'index'
621 assert_template 'index'
605 assert_not_nil assigns(:entries)
622 assert_not_nil assigns(:entries)
606 assert_equal 2, assigns(:entries).size
623 assert_equal 2, assigns(:entries).size
607 assert_not_nil assigns(:total_hours)
624 assert_not_nil assigns(:total_hours)
608 assert_equal 154.25, assigns(:total_hours)
625 assert_equal 154.25, assigns(:total_hours)
609 # display all time
626 # display all time
610 assert_nil assigns(:from)
627 assert_nil assigns(:from)
611 assert_nil assigns(:to)
628 assert_nil assigns(:to)
612 assert_tag :form,
629 assert_tag :form,
613 :attributes => {:action => "/issues/1/time_entries", :id => 'query_form'}
630 :attributes => {:action => "/issues/1/time_entries", :id => 'query_form'}
614 end
631 end
615
632
616 def test_index_should_sort_by_spent_on_and_created_on
633 def test_index_should_sort_by_spent_on_and_created_on
617 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
634 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
618 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
635 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
619 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
636 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
620
637
621 get :index, :project_id => 1,
638 get :index, :project_id => 1,
622 :f => ['spent_on'],
639 :f => ['spent_on'],
623 :op => {'spent_on' => '><'},
640 :op => {'spent_on' => '><'},
624 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
641 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
625 assert_response :success
642 assert_response :success
626 assert_equal [t2, t1, t3], assigns(:entries)
643 assert_equal [t2, t1, t3], assigns(:entries)
627
644
628 get :index, :project_id => 1,
645 get :index, :project_id => 1,
629 :f => ['spent_on'],
646 :f => ['spent_on'],
630 :op => {'spent_on' => '><'},
647 :op => {'spent_on' => '><'},
631 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
648 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
632 :sort => 'spent_on'
649 :sort => 'spent_on'
633 assert_response :success
650 assert_response :success
634 assert_equal [t3, t1, t2], assigns(:entries)
651 assert_equal [t3, t1, t2], assigns(:entries)
635 end
652 end
636
653
637 def test_index_with_filter_on_issue_custom_field
654 def test_index_with_filter_on_issue_custom_field
638 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
655 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
639 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
656 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
640
657
641 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
658 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
642 assert_response :success
659 assert_response :success
643 assert_equal [entry], assigns(:entries)
660 assert_equal [entry], assigns(:entries)
644 end
661 end
645
662
646 def test_index_with_issue_custom_field_column
663 def test_index_with_issue_custom_field_column
647 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
664 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
648 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
665 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
649
666
650 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
667 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
651 assert_response :success
668 assert_response :success
652 assert_include :'issue.cf_2', assigns(:query).column_names
669 assert_include :'issue.cf_2', assigns(:query).column_names
653 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
670 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
654 end
671 end
655
672
656 def test_index_with_time_entry_custom_field_column
673 def test_index_with_time_entry_custom_field_column
657 field = TimeEntryCustomField.generate!(:field_format => 'string')
674 field = TimeEntryCustomField.generate!(:field_format => 'string')
658 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
675 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
659 field_name = "cf_#{field.id}"
676 field_name = "cf_#{field.id}"
660
677
661 get :index, :c => ["hours", field_name]
678 get :index, :c => ["hours", field_name]
662 assert_response :success
679 assert_response :success
663 assert_include field_name.to_sym, assigns(:query).column_names
680 assert_include field_name.to_sym, assigns(:query).column_names
664 assert_select "td.#{field_name}", :text => 'CF Value'
681 assert_select "td.#{field_name}", :text => 'CF Value'
665 end
682 end
666
683
667 def test_index_with_time_entry_custom_field_sorting
684 def test_index_with_time_entry_custom_field_sorting
668 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
685 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
669 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
686 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
670 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
687 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
671 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
688 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
672 field_name = "cf_#{field.id}"
689 field_name = "cf_#{field.id}"
673
690
674 get :index, :c => ["hours", field_name], :sort => field_name
691 get :index, :c => ["hours", field_name], :sort => field_name
675 assert_response :success
692 assert_response :success
676 assert_include field_name.to_sym, assigns(:query).column_names
693 assert_include field_name.to_sym, assigns(:query).column_names
677 assert_select "th a.sort", :text => 'String Field'
694 assert_select "th a.sort", :text => 'String Field'
678
695
679 # Make sure that values are properly sorted
696 # Make sure that values are properly sorted
680 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
697 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
681 assert_equal 3, values.size
698 assert_equal 3, values.size
682 assert_equal values.sort, values
699 assert_equal values.sort, values
683 end
700 end
684
701
685 def test_index_atom_feed
702 def test_index_atom_feed
686 get :index, :project_id => 1, :format => 'atom'
703 get :index, :project_id => 1, :format => 'atom'
687 assert_response :success
704 assert_response :success
688 assert_equal 'application/atom+xml', @response.content_type
705 assert_equal 'application/atom+xml', @response.content_type
689 assert_not_nil assigns(:items)
706 assert_not_nil assigns(:items)
690 assert assigns(:items).first.is_a?(TimeEntry)
707 assert assigns(:items).first.is_a?(TimeEntry)
691 end
708 end
692
709
693 def test_index_at_project_level_should_include_csv_export_dialog
710 def test_index_at_project_level_should_include_csv_export_dialog
694 get :index, :project_id => 'ecookbook',
711 get :index, :project_id => 'ecookbook',
695 :f => ['spent_on'],
712 :f => ['spent_on'],
696 :op => {'spent_on' => '>='},
713 :op => {'spent_on' => '>='},
697 :v => {'spent_on' => ['2007-04-01']},
714 :v => {'spent_on' => ['2007-04-01']},
698 :c => ['spent_on', 'user']
715 :c => ['spent_on', 'user']
699 assert_response :success
716 assert_response :success
700
717
701 assert_select '#csv-export-options' do
718 assert_select '#csv-export-options' do
702 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
719 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
703 # filter
720 # filter
704 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
721 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
705 assert_select 'input[name=?][value=?]', 'op[spent_on]', '&gt;='
722 assert_select 'input[name=?][value=?]', 'op[spent_on]', '&gt;='
706 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
723 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
707 # columns
724 # columns
708 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
725 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
709 assert_select 'input[name=?][value=?]', 'c[]', 'user'
726 assert_select 'input[name=?][value=?]', 'c[]', 'user'
710 assert_select 'input[name=?]', 'c[]', 2
727 assert_select 'input[name=?]', 'c[]', 2
711 end
728 end
712 end
729 end
713 end
730 end
714
731
715 def test_index_cross_project_should_include_csv_export_dialog
732 def test_index_cross_project_should_include_csv_export_dialog
716 get :index
733 get :index
717 assert_response :success
734 assert_response :success
718
735
719 assert_select '#csv-export-options' do
736 assert_select '#csv-export-options' do
720 assert_select 'form[action=?][method=get]', '/time_entries.csv'
737 assert_select 'form[action=?][method=get]', '/time_entries.csv'
721 end
738 end
722 end
739 end
723
740
724 def test_index_at_issue_level_should_include_csv_export_dialog
741 def test_index_at_issue_level_should_include_csv_export_dialog
725 get :index, :issue_id => 3
742 get :index, :issue_id => 3
726 assert_response :success
743 assert_response :success
727
744
728 assert_select '#csv-export-options' do
745 assert_select '#csv-export-options' do
729 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
746 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
730 end
747 end
731 end
748 end
732
749
733 def test_index_csv_all_projects
750 def test_index_csv_all_projects
734 Setting.date_format = '%m/%d/%Y'
751 Setting.date_format = '%m/%d/%Y'
735 get :index, :format => 'csv'
752 get :index, :format => 'csv'
736 assert_response :success
753 assert_response :success
737 assert_equal 'text/csv; header=present', response.content_type
754 assert_equal 'text/csv; header=present', response.content_type
738 end
755 end
739
756
740 def test_index_csv
757 def test_index_csv
741 Setting.date_format = '%m/%d/%Y'
758 Setting.date_format = '%m/%d/%Y'
742 get :index, :project_id => 1, :format => 'csv'
759 get :index, :project_id => 1, :format => 'csv'
743 assert_response :success
760 assert_response :success
744 assert_equal 'text/csv; header=present', response.content_type
761 assert_equal 'text/csv; header=present', response.content_type
745 end
762 end
746
763
747 def test_index_csv_should_fill_issue_column_with_tracker_id_and_subject
764 def test_index_csv_should_fill_issue_column_with_tracker_id_and_subject
748 issue = Issue.find(1)
765 issue = Issue.find(1)
749 entry = TimeEntry.generate!(:issue => issue, :comments => "Issue column content test")
766 entry = TimeEntry.generate!(:issue => issue, :comments => "Issue column content test")
750
767
751 get :index, :format => 'csv'
768 get :index, :format => 'csv'
752 line = response.body.split("\n").detect {|l| l.include?(entry.comments)}
769 line = response.body.split("\n").detect {|l| l.include?(entry.comments)}
753 assert_not_nil line
770 assert_not_nil line
754 assert_include "#{issue.tracker} #1: #{issue.subject}", line
771 assert_include "#{issue.tracker} #1: #{issue.subject}", line
755 end
772 end
756 end
773 end
General Comments 0
You need to be logged in to leave comments. Login now