Revision 1298:4f746d8966dd .svn/pristine/41

View differences:

.svn/pristine/41/413080d862dfb1417751d3ce6800014b0d3f9e36.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2011  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
require File.expand_path('../../test_helper', __FILE__)
19

  
20
class JournalObserverTest < ActiveSupport::TestCase
21
  fixtures :issues, :issue_statuses, :journals, :journal_details
22

  
23
  def setup
24
    ActionMailer::Base.deliveries.clear
25
    @journal = Journal.find 1
26
  end
27

  
28
  # context: issue_updated notified_events
29
  def test_create_should_send_email_notification_with_issue_updated
30
    Setting.notified_events = ['issue_updated']
31
    issue = Issue.find(:first)
32
    user = User.find(:first)
33
    journal = issue.init_journal(user, issue)
34

  
35
    assert journal.save
36
    assert_equal 1, ActionMailer::Base.deliveries.size
37
  end
38

  
39
  def test_create_should_not_send_email_notification_with_notify_set_to_false
40
    Setting.notified_events = ['issue_updated']
41
    issue = Issue.find(:first)
42
    user = User.find(:first)
43
    journal = issue.init_journal(user, issue)
44
    journal.notify = false
45

  
46
    assert journal.save
47
    assert_equal 0, ActionMailer::Base.deliveries.size
48
  end
49

  
50
  def test_create_should_not_send_email_notification_without_issue_updated
51
    Setting.notified_events = []
52
    issue = Issue.find(:first)
53
    user = User.find(:first)
54
    journal = issue.init_journal(user, issue)
55

  
56
    assert journal.save
57
    assert_equal 0, ActionMailer::Base.deliveries.size
58
  end
59

  
60
  # context: issue_note_added notified_events
61
  def test_create_should_send_email_notification_with_issue_note_added
62
    Setting.notified_events = ['issue_note_added']
63
    issue = Issue.find(:first)
64
    user = User.find(:first)
65
    journal = issue.init_journal(user, issue)
66
    journal.notes = 'This update has a note'
67

  
68
    assert journal.save
69
    assert_equal 1, ActionMailer::Base.deliveries.size
70
  end
71

  
72
  def test_create_should_not_send_email_notification_without_issue_note_added
73
    Setting.notified_events = []
74
    issue = Issue.find(:first)
75
    user = User.find(:first)
76
    journal = issue.init_journal(user, issue)
77
    journal.notes = 'This update has a note'
78

  
79
    assert journal.save
80
    assert_equal 0, ActionMailer::Base.deliveries.size
81
  end
82

  
83
  # context: issue_status_updated notified_events
84
  def test_create_should_send_email_notification_with_issue_status_updated
85
    Setting.notified_events = ['issue_status_updated']
86
    issue = Issue.find(:first)
87
    user = User.find(:first)
88
    issue.init_journal(user, issue)
89
    issue.status = IssueStatus.last
90

  
91
    assert issue.save
92
    assert_equal 1, ActionMailer::Base.deliveries.size
93
  end
94

  
95
  def test_create_should_not_send_email_notification_without_issue_status_updated
96
    Setting.notified_events = []
97
    issue = Issue.find(:first)
98
    user = User.find(:first)
99
    issue.init_journal(user, issue)
100
    issue.status = IssueStatus.last
101

  
102
    assert issue.save
103
    assert_equal 0, ActionMailer::Base.deliveries.size
104
  end
105

  
106
  # context: issue_priority_updated notified_events
107
  def test_create_should_send_email_notification_with_issue_priority_updated
108
    Setting.notified_events = ['issue_priority_updated']
109
    issue = Issue.find(:first)
110
    user = User.find(:first)
111
    issue.init_journal(user, issue)
112
    issue.priority = IssuePriority.last
113

  
114
    assert issue.save
115
    assert_equal 1, ActionMailer::Base.deliveries.size
116
  end
117

  
118
  def test_create_should_not_send_email_notification_without_issue_priority_updated
119
    Setting.notified_events = []
120
    issue = Issue.find(:first)
121
    user = User.find(:first)
122
    issue.init_journal(user, issue)
123
    issue.priority = IssuePriority.last
124

  
125
    assert issue.save
126
    assert_equal 0, ActionMailer::Base.deliveries.size
127
  end
128
end
.svn/pristine/41/414d847bcbb5d1b481c5b6e074d2d95ff30c291d.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2013  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
require File.expand_path('../../test_helper', __FILE__)
19

  
20
class UsersTest < ActionController::IntegrationTest
21
  fixtures :users
22

  
23
  def test_destroy_should_not_accept_get_requests
24
    assert_no_difference 'User.count' do
25
      get '/users/destroy/2', {}, credentials('admin')
26
      assert_response 404
27
    end
28
  end
29
end
.svn/pristine/41/41666c76b3a63b119e23476dd87dae257e309108.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2013  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 NewsController < ApplicationController
19
  default_search_scope :news
20
  model_object News
21
  before_filter :find_model_object, :except => [:new, :create, :index]
22
  before_filter :find_project_from_association, :except => [:new, :create, :index]
23
  before_filter :find_project_by_project_id, :only => [:new, :create]
24
  before_filter :authorize, :except => [:index]
25
  before_filter :find_optional_project, :only => :index
26
  accept_rss_auth :index
27
  accept_api_auth :index
28

  
29
  helper :watchers
30
  helper :attachments
31

  
32
  def index
33
    case params[:format]
34
    when 'xml', 'json'
35
      @offset, @limit = api_offset_and_limit
36
    else
37
      @limit =  10
38
    end
39

  
40
    scope = @project ? @project.news.visible : News.visible
41

  
42
    @news_count = scope.count
43
    @news_pages = Paginator.new @news_count, @limit, params['page']
44
    @offset ||= @news_pages.offset
45
    @newss = scope.all(:include => [:author, :project],
46
                                       :order => "#{News.table_name}.created_on DESC",
47
                                       :offset => @offset,
48
                                       :limit => @limit)
49

  
50
    respond_to do |format|
51
      format.html {
52
        @news = News.new # for adding news inline
53
        render :layout => false if request.xhr?
54
      }
55
      format.api
56
      format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
57
    end
58
  end
59

  
60
  def show
61
    @comments = @news.comments
62
    @comments.reverse! if User.current.wants_comments_in_reverse_order?
63
  end
64

  
65
  def new
66
    @news = News.new(:project => @project, :author => User.current)
67
  end
68

  
69
  def create
70
    @news = News.new(:project => @project, :author => User.current)
71
    @news.safe_attributes = params[:news]
72
    @news.save_attachments(params[:attachments])
73
    if @news.save
74
      render_attachment_warning_if_needed(@news)
75
      flash[:notice] = l(:notice_successful_create)
76
      redirect_to project_news_index_path(@project)
77
    else
78
      render :action => 'new'
79
    end
80
  end
81

  
82
  def edit
83
  end
84

  
85
  def update
86
    @news.safe_attributes = params[:news]
87
    @news.save_attachments(params[:attachments])
88
    if @news.save
89
      render_attachment_warning_if_needed(@news)
90
      flash[:notice] = l(:notice_successful_update)
91
      redirect_to news_path(@news)
92
    else
93
      render :action => 'edit'
94
    end
95
  end
96

  
97
  def destroy
98
    @news.destroy
99
    redirect_to project_news_index_path(@project)
100
  end
101

  
102
  private
103

  
104
  def find_optional_project
105
    return true unless params[:project_id]
106
    @project = Project.find(params[:project_id])
107
    authorize
108
  rescue ActiveRecord::RecordNotFound
109
    render_404
110
  end
111
end
.svn/pristine/41/4171661e14f170beefe360373ffc236afc3efe66.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2013  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
require File.expand_path('../../../test_helper', __FILE__)
19

  
20
class ActivitiesHelperTest < ActionView::TestCase
21
  include ActivitiesHelper
22

  
23
  class MockEvent
24
    attr_reader :event_datetime, :event_group, :name
25

  
26
    def initialize(group=nil)
27
      @@count ||= 0
28
      @name = "e#{@@count}"
29
      @event_datetime = Time.now + @@count.hours
30
      @event_group = group || self
31
      @@count += 1
32
    end
33

  
34
    def self.clear
35
      @@count = 0
36
    end
37
  end
38

  
39
  def setup
40
    MockEvent.clear
41
  end
42

  
43
  def test_sort_activity_events_should_sort_by_datetime
44
    events = []
45
    events << MockEvent.new
46
    events << MockEvent.new
47
    events << MockEvent.new
48

  
49
    assert_equal [
50
        ['e2', false],
51
        ['e1', false],
52
        ['e0', false]
53
      ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
54
  end
55

  
56
  def test_sort_activity_events_should_group_events
57
    events = []
58
    events << MockEvent.new
59
    events << MockEvent.new(events[0])
60
    events << MockEvent.new(events[0])
61

  
62
    assert_equal [
63
        ['e2', false],
64
        ['e1', true],
65
        ['e0', true]
66
      ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
67
  end
68

  
69
  def test_sort_activity_events_with_group_not_in_set_should_group_events
70
    e = MockEvent.new
71
    events = []
72
    events << MockEvent.new(e)
73
    events << MockEvent.new(e)
74

  
75
    assert_equal [
76
        ['e2', false],
77
        ['e1', true]
78
      ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
79
  end
80

  
81
  def test_sort_activity_events_should_sort_by_datetime_and_group
82
    events = []
83
    events << MockEvent.new
84
    events << MockEvent.new
85
    events << MockEvent.new
86
    events << MockEvent.new(events[1])
87
    events << MockEvent.new(events[2])
88
    events << MockEvent.new
89
    events << MockEvent.new(events[2])
90

  
91
    assert_equal [
92
        ['e6', false],
93
        ['e4', true],
94
        ['e2', true],
95
        ['e5', false],
96
        ['e3', false],
97
        ['e1', true],
98
        ['e0', false]
99
      ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
100
  end
101
end
.svn/pristine/41/41bb9cc50bdf02df6fa0e55ba5d0a45d87f52436.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2013  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 IssueRelation < ActiveRecord::Base
19
  # Class used to represent the relations of an issue
20
  class Relations < Array
21
    include Redmine::I18n
22

  
23
    def initialize(issue, *args)
24
      @issue = issue
25
      super(*args)
26
    end
27

  
28
    def to_s(*args)
29
      map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
30
    end
31
  end
32

  
33
  belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
34
  belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
35

  
36
  TYPE_RELATES      = "relates"
37
  TYPE_DUPLICATES   = "duplicates"
38
  TYPE_DUPLICATED   = "duplicated"
39
  TYPE_BLOCKS       = "blocks"
40
  TYPE_BLOCKED      = "blocked"
41
  TYPE_PRECEDES     = "precedes"
42
  TYPE_FOLLOWS      = "follows"
43
  TYPE_COPIED_TO    = "copied_to"
44
  TYPE_COPIED_FROM  = "copied_from"
45

  
46
  TYPES = {
47
    TYPE_RELATES =>     { :name => :label_relates_to, :sym_name => :label_relates_to,
48
                          :order => 1, :sym => TYPE_RELATES },
49
    TYPE_DUPLICATES =>  { :name => :label_duplicates, :sym_name => :label_duplicated_by,
50
                          :order => 2, :sym => TYPE_DUPLICATED },
51
    TYPE_DUPLICATED =>  { :name => :label_duplicated_by, :sym_name => :label_duplicates,
52
                          :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
53
    TYPE_BLOCKS =>      { :name => :label_blocks, :sym_name => :label_blocked_by,
54
                          :order => 4, :sym => TYPE_BLOCKED },
55
    TYPE_BLOCKED =>     { :name => :label_blocked_by, :sym_name => :label_blocks,
56
                          :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
57
    TYPE_PRECEDES =>    { :name => :label_precedes, :sym_name => :label_follows,
58
                          :order => 6, :sym => TYPE_FOLLOWS },
59
    TYPE_FOLLOWS =>     { :name => :label_follows, :sym_name => :label_precedes,
60
                          :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
61
    TYPE_COPIED_TO =>   { :name => :label_copied_to, :sym_name => :label_copied_from,
62
                          :order => 8, :sym => TYPE_COPIED_FROM },
63
    TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
64
                          :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
65
  }.freeze
66

  
67
  validates_presence_of :issue_from, :issue_to, :relation_type
68
  validates_inclusion_of :relation_type, :in => TYPES.keys
69
  validates_numericality_of :delay, :allow_nil => true
70
  validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
71
  validate :validate_issue_relation
72

  
73
  attr_protected :issue_from_id, :issue_to_id
74
  before_save :handle_issue_order
75

  
76
  def visible?(user=User.current)
77
    (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user))
78
  end
79

  
80
  def deletable?(user=User.current)
81
    visible?(user) &&
82
      ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) ||
83
        (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project)))
84
  end
85

  
86
  def initialize(attributes=nil, *args)
87
    super
88
    if new_record?
89
      if relation_type.blank?
90
        self.relation_type = IssueRelation::TYPE_RELATES
91
      end
92
    end
93
  end
94

  
95
  def validate_issue_relation
96
    if issue_from && issue_to
97
      errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
98
      unless issue_from.project_id == issue_to.project_id ||
99
                Setting.cross_project_issue_relations?
100
        errors.add :issue_to_id, :not_same_project
101
      end
102
      # detect circular dependencies depending wether the relation should be reversed
103
      if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
104
        errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
105
      else
106
        errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
107
      end
108
      if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
109
        errors.add :base, :cant_link_an_issue_with_a_descendant
110
      end
111
    end
112
  end
113

  
114
  def other_issue(issue)
115
    (self.issue_from_id == issue.id) ? issue_to : issue_from
116
  end
117

  
118
  # Returns the relation type for +issue+
119
  def relation_type_for(issue)
120
    if TYPES[relation_type]
121
      if self.issue_from_id == issue.id
122
        relation_type
123
      else
124
        TYPES[relation_type][:sym]
125
      end
126
    end
127
  end
128

  
129
  def label_for(issue)
130
    TYPES[relation_type] ?
131
        TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
132
        :unknow
133
  end
134

  
135
  def css_classes_for(issue)
136
    "rel-#{relation_type_for(issue)}"
137
  end
138

  
139
  def handle_issue_order
140
    reverse_if_needed
141

  
142
    if TYPE_PRECEDES == relation_type
143
      self.delay ||= 0
144
    else
145
      self.delay = nil
146
    end
147
    set_issue_to_dates
148
  end
149

  
150
  def set_issue_to_dates
151
    soonest_start = self.successor_soonest_start
152
    if soonest_start && issue_to
153
      issue_to.reschedule_on!(soonest_start)
154
    end
155
  end
156

  
157
  def successor_soonest_start
158
    if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
159
           (issue_from.start_date || issue_from.due_date)
160
      (issue_from.due_date || issue_from.start_date) + 1 + delay
161
    end
162
  end
163

  
164
  def <=>(relation)
165
    r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
166
    r == 0 ? id <=> relation.id : r
167
  end
168

  
169
  private
170

  
171
  # Reverses the relation if needed so that it gets stored in the proper way
172
  # Should not be reversed before validation so that it can be displayed back
173
  # as entered on new relation form
174
  def reverse_if_needed
175
    if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
176
      issue_tmp = issue_to
177
      self.issue_to = issue_from
178
      self.issue_from = issue_tmp
179
      self.relation_type = TYPES[relation_type][:reverse]
180
    end
181
  end
182
end

Also available in: Unified diff