Revision 1298:4f746d8966dd .svn/pristine/2d

View differences:

.svn/pristine/2d/2d1b6f7772fdd853c88983e5f12b084f72e3159d.svn-base
1
class AddCommitAccessPermission < ActiveRecord::Migration
2
  def self.up
3
    Role.all.select { |r| not r.builtin? }.each do |r|
4
      r.add_permission!(:commit_access)
5
    end
6
  end
7

  
8
  def self.down
9
    Role.all.select { |r| not r.builtin? }.each do |r|
10
      r.remove_permission!(:commit_access)
11
    end
12
  end
13
end
.svn/pristine/2d/2d2fc73f05e5360a853859d4a6d6261e16112400.svn-base
1
<div class="contextual">
2
<%= link_to l(:label_profile), user_path(@user), :class => 'icon icon-user' %>
3
<%= change_status_link(@user) %>
4
<%= link_to(l(:button_delete), @user, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del') if User.current != @user %>
5
</div>
6

  
7
<h2><%= link_to l(:label_user_plural), users_path %> &#187; <%=h @user.login %></h2>
8

  
9
<%= render_tabs user_settings_tabs %>
10

  
11
<% html_title(l(:label_user), @user.login, l(:label_administration)) -%>
.svn/pristine/2d/2d3d652686003e0e75f2451ee9050de946a60dda.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
# Generic exception for when the AuthSource can not be reached
19
# (eg. can not connect to the LDAP)
20
class AuthSourceException < Exception; end
21
class AuthSourceTimeoutException < AuthSourceException; end
22

  
23
class AuthSource < ActiveRecord::Base
24
  include Redmine::SubclassFactory
25
  include Redmine::Ciphering
26

  
27
  has_many :users
28

  
29
  validates_presence_of :name
30
  validates_uniqueness_of :name
31
  validates_length_of :name, :maximum => 60
32

  
33
  def authenticate(login, password)
34
  end
35

  
36
  def test_connection
37
  end
38

  
39
  def auth_method_name
40
    "Abstract"
41
  end
42

  
43
  def account_password
44
    read_ciphered_attribute(:account_password)
45
  end
46

  
47
  def account_password=(arg)
48
    write_ciphered_attribute(:account_password, arg)
49
  end
50

  
51
  def searchable?
52
    false
53
  end
54

  
55
  def self.search(q)
56
    results = []
57
    AuthSource.all.each do |source|
58
      begin
59
        if source.searchable?
60
          results += source.search(q)
61
        end
62
      rescue AuthSourceException => e
63
        logger.error "Error while searching users in #{source.name}: #{e.message}"
64
      end
65
    end
66
    results
67
  end
68

  
69
  def allow_password_changes?
70
    self.class.allow_password_changes?
71
  end
72

  
73
  # Does this auth source backend allow password changes?
74
  def self.allow_password_changes?
75
    false
76
  end
77

  
78
  # Try to authenticate a user not yet registered against available sources
79
  def self.authenticate(login, password)
80
    AuthSource.where(:onthefly_register => true).all.each do |source|
81
      begin
82
        logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
83
        attrs = source.authenticate(login, password)
84
      rescue => e
85
        logger.error "Error during authentication: #{e.message}"
86
        attrs = nil
87
      end
88
      return attrs if attrs
89
    end
90
    return nil
91
  end
92
end
.svn/pristine/2d/2d611b348b4b0790a149102dbbb27f7bf647e472.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 Redmine::PluginTest < ActiveSupport::TestCase
21

  
22
  def setup
23
    @klass = Redmine::Plugin
24
    # In case some real plugins are installed
25
    @klass.clear
26
  end
27

  
28
  def teardown
29
    @klass.clear
30
  end
31

  
32
  def test_register
33
    @klass.register :foo do
34
      name 'Foo plugin'
35
      url 'http://example.net/plugins/foo'
36
      author 'John Smith'
37
      author_url 'http://example.net/jsmith'
38
      description 'This is a test plugin'
39
      version '0.0.1'
40
      settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
41
    end
42

  
43
    assert_equal 1, @klass.all.size
44

  
45
    plugin = @klass.find('foo')
46
    assert plugin.is_a?(Redmine::Plugin)
47
    assert_equal :foo, plugin.id
48
    assert_equal 'Foo plugin', plugin.name
49
    assert_equal 'http://example.net/plugins/foo', plugin.url
50
    assert_equal 'John Smith', plugin.author
51
    assert_equal 'http://example.net/jsmith', plugin.author_url
52
    assert_equal 'This is a test plugin', plugin.description
53
    assert_equal '0.0.1', plugin.version
54
  end
55

  
56
  def test_installed
57
    @klass.register(:foo) {}
58

  
59
    assert_equal true, @klass.installed?(:foo)
60
    assert_equal false, @klass.installed?(:bar)
61
  end
62

  
63
  def test_menu
64
    assert_difference 'Redmine::MenuManager.items(:project_menu).size' do
65
      @klass.register :foo do
66
        menu :project_menu, :foo_menu_item, '/foo', :caption => 'Foo'
67
      end
68
    end
69
    menu_item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item}
70
    assert_not_nil menu_item
71
    assert_equal 'Foo', menu_item.caption
72
    assert_equal '/foo', menu_item.url
73
  end
74

  
75
  def test_delete_menu_item
76
    Redmine::MenuManager.map(:project_menu).push(:foo_menu_item, '/foo', :caption => 'Foo')
77

  
78
    assert_difference 'Redmine::MenuManager.items(:project_menu).size', -1 do
79
      @klass.register :foo do
80
        delete_menu_item :project_menu, :foo_menu_item
81
      end
82
    end
83
    assert_nil Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item}
84
  end
85

  
86
  def test_requires_redmine
87
    plugin = Redmine::Plugin.register(:foo) {}
88
    Redmine::VERSION.stubs(:to_a).returns([2, 1, 3, "stable", 10817])
89

  
90
    # Specific version without hash
91
    assert plugin.requires_redmine('2.1.3')
92
    assert plugin.requires_redmine('2.1')
93
    assert_raise Redmine::PluginRequirementError do
94
      plugin.requires_redmine('2.1.4')
95
    end
96
    assert_raise Redmine::PluginRequirementError do
97
      plugin.requires_redmine('2.2')
98
    end
99

  
100
    # Specific version
101
    assert plugin.requires_redmine(:version => '2.1.3')
102
    assert plugin.requires_redmine(:version => ['2.1.3', '2.2.0'])
103
    assert plugin.requires_redmine(:version => '2.1')
104
    assert_raise Redmine::PluginRequirementError do
105
      plugin.requires_redmine(:version => '2.2.0')
106
    end
107
    assert_raise Redmine::PluginRequirementError do
108
      plugin.requires_redmine(:version => ['2.1.4', '2.2.0'])
109
    end
110
    assert_raise Redmine::PluginRequirementError do
111
      plugin.requires_redmine(:version => '2.2')
112
    end
113

  
114
    # Version range
115
    assert plugin.requires_redmine(:version => '2.0.0'..'2.2.4')
116
    assert plugin.requires_redmine(:version => '2.1.3'..'2.2.4')
117
    assert plugin.requires_redmine(:version => '2.0.0'..'2.1.3')
118
    assert plugin.requires_redmine(:version => '2.0'..'2.2')
119
    assert plugin.requires_redmine(:version => '2.1'..'2.2')
120
    assert plugin.requires_redmine(:version => '2.0'..'2.1')
121
    assert_raise Redmine::PluginRequirementError do
122
      plugin.requires_redmine(:version => '2.1.4'..'2.2.4')
123
    end
124

  
125

  
126
    # Version or higher
127
    assert plugin.requires_redmine(:version_or_higher => '0.1.0')
128
    assert plugin.requires_redmine(:version_or_higher => '2.1.3')
129
    assert plugin.requires_redmine(:version_or_higher => '2.1')
130
    assert_raise Redmine::PluginRequirementError do
131
      plugin.requires_redmine(:version_or_higher => '2.2.0')
132
    end
133
    assert_raise Redmine::PluginRequirementError do
134
      plugin.requires_redmine(:version_or_higher => '2.2')
135
    end
136
  end
137

  
138
  def test_requires_redmine_plugin
139
    test = self
140
    other_version = '0.5.0'
141

  
142
    @klass.register :other do
143
      name 'Other'
144
      version other_version
145
    end
146

  
147
    @klass.register :foo do
148
      test.assert requires_redmine_plugin(:other, :version_or_higher => '0.1.0')
149
      test.assert requires_redmine_plugin(:other, :version_or_higher => other_version)
150
      test.assert requires_redmine_plugin(:other, other_version)
151
      test.assert_raise Redmine::PluginRequirementError do
152
        requires_redmine_plugin(:other, :version_or_higher => '99.0.0')
153
      end
154

  
155
      test.assert requires_redmine_plugin(:other, :version => other_version)
156
      test.assert requires_redmine_plugin(:other, :version => [other_version, '99.0.0'])
157
      test.assert_raise Redmine::PluginRequirementError do
158
        requires_redmine_plugin(:other, :version => '99.0.0')
159
      end
160
      test.assert_raise Redmine::PluginRequirementError do
161
        requires_redmine_plugin(:other, :version => ['98.0.0', '99.0.0'])
162
      end
163
      # Missing plugin
164
      test.assert_raise Redmine::PluginNotFound do
165
        requires_redmine_plugin(:missing, :version_or_higher => '0.1.0')
166
      end
167
      test.assert_raise Redmine::PluginNotFound do
168
        requires_redmine_plugin(:missing, '0.1.0')
169
      end
170
      test.assert_raise Redmine::PluginNotFound do
171
        requires_redmine_plugin(:missing, :version => '0.1.0')
172
      end
173

  
174
    end
175
  end
176
end
.svn/pristine/2d/2d85d0783f59c365e1251c736bcbc44da38bc043.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 WikiPageTest < ActiveSupport::TestCase
21
  fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
22

  
23
  def setup
24
    @wiki = Wiki.find(1)
25
    @page = @wiki.pages.first
26
  end
27

  
28
  def test_create
29
    page = WikiPage.new(:wiki => @wiki)
30
    assert !page.save
31
    assert_equal 1, page.errors.count
32

  
33
    page.title = "Page"
34
    assert page.save
35
    page.reload
36
    assert !page.protected?
37

  
38
    @wiki.reload
39
    assert @wiki.pages.include?(page)
40
  end
41

  
42
  def test_sidebar_should_be_protected_by_default
43
    page = @wiki.find_or_new_page('sidebar')
44
    assert page.new_record?
45
    assert page.protected?
46
  end
47

  
48
  def test_find_or_new_page
49
    page = @wiki.find_or_new_page("CookBook documentation")
50
    assert_kind_of WikiPage, page
51
    assert !page.new_record?
52

  
53
    page = @wiki.find_or_new_page("Non existing page")
54
    assert_kind_of WikiPage, page
55
    assert page.new_record?
56
  end
57

  
58
  def test_parent_title
59
    page = WikiPage.find_by_title('Another_page')
60
    assert_nil page.parent_title
61

  
62
    page = WikiPage.find_by_title('Page_with_an_inline_image')
63
    assert_equal 'CookBook documentation', page.parent_title
64
  end
65

  
66
  def test_assign_parent
67
    page = WikiPage.find_by_title('Another_page')
68
    page.parent_title = 'CookBook documentation'
69
    assert page.save
70
    page.reload
71
    assert_equal WikiPage.find_by_title('CookBook_documentation'), page.parent
72
  end
73

  
74
  def test_unassign_parent
75
    page = WikiPage.find_by_title('Page_with_an_inline_image')
76
    page.parent_title = ''
77
    assert page.save
78
    page.reload
79
    assert_nil page.parent
80
  end
81

  
82
  def test_parent_validation
83
    page = WikiPage.find_by_title('CookBook_documentation')
84

  
85
    # A page that doesn't exist
86
    page.parent_title = 'Unknown title'
87
    assert !page.save
88
    assert_include I18n.translate('activerecord.errors.messages.invalid'),
89
                   page.errors[:parent_title]
90
    # A child page
91
    page.parent_title = 'Page_with_an_inline_image'
92
    assert !page.save
93
    assert_include I18n.translate('activerecord.errors.messages.circular_dependency'),
94
                   page.errors[:parent_title]
95
    # The page itself
96
    page.parent_title = 'CookBook_documentation'
97
    assert !page.save
98
    assert_include I18n.translate('activerecord.errors.messages.circular_dependency'),
99
                   page.errors[:parent_title]
100
    page.parent_title = 'Another_page'
101
    assert page.save
102
  end
103

  
104
  def test_destroy
105
    page = WikiPage.find(1)
106
    page.destroy
107
    assert_nil WikiPage.find_by_id(1)
108
    # make sure that page content and its history are deleted
109
    assert WikiContent.find_all_by_page_id(1).empty?
110
    assert WikiContent.versioned_class.find_all_by_page_id(1).empty?
111
  end
112

  
113
  def test_destroy_should_not_nullify_children
114
    page = WikiPage.find(2)
115
    child_ids = page.child_ids
116
    assert child_ids.any?
117
    page.destroy
118
    assert_nil WikiPage.find_by_id(2)
119

  
120
    children = WikiPage.find_all_by_id(child_ids)
121
    assert_equal child_ids.size, children.size
122
    children.each do |child|
123
      assert_nil child.parent_id
124
    end
125
  end
126

  
127
  def test_updated_on_eager_load
128
    page = WikiPage.with_updated_on.first(:order => 'id')
129
    assert page.is_a?(WikiPage)
130
    assert_not_nil page.read_attribute(:updated_on)
131
    assert_equal Time.gm(2007, 3, 6, 23, 10, 51), page.content.updated_on
132
    assert_equal page.content.updated_on, page.updated_on
133
    assert_not_nil page.read_attribute(:version)
134
  end
135

  
136
  def test_descendants
137
    page = WikiPage.create!(:wiki => @wiki, :title => 'Parent')
138
    child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page)
139
    child11 = WikiPage.create!(:wiki => @wiki, :title => 'Child11', :parent => child1)
140
    child111 = WikiPage.create!(:wiki => @wiki, :title => 'Child111', :parent => child11)
141
    child2 = WikiPage.create!(:wiki => @wiki, :title => 'Child2', :parent => page)
142

  
143
    assert_equal %w(Child1 Child11 Child111 Child2), page.descendants.map(&:title).sort
144
    assert_equal %w(Child1 Child11 Child111 Child2), page.descendants(nil).map(&:title).sort
145
    assert_equal %w(Child1 Child11 Child2), page.descendants(2).map(&:title).sort
146
    assert_equal %w(Child1 Child2), page.descendants(1).map(&:title).sort
147

  
148
    assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants.map(&:title).sort
149
    assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants(nil).map(&:title).sort
150
    assert_equal %w(Child1 Child11 Child2 Parent), page.self_and_descendants(2).map(&:title).sort
151
    assert_equal %w(Child1 Child2 Parent), page.self_and_descendants(1).map(&:title).sort
152
  end
153

  
154
  def test_diff_for_page_with_deleted_version_should_pick_the_previous_available_version
155
    WikiContent::Version.find_by_page_id_and_version(1, 2).destroy
156

  
157
    page = WikiPage.find(1)
158
    diff = page.diff(3)
159
    assert_not_nil diff
160
    assert_equal 3, diff.content_to.version
161
    assert_equal 1, diff.content_from.version
162
  end
163
end
.svn/pristine/2d/2dbd0564d495b47bea2303b32f1609a8bf5c4b1a.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 RoutingJournalsTest < ActionController::IntegrationTest
21
  def test_journals
22
    assert_routing(
23
        { :method => 'post', :path => "/issues/1/quoted" },
24
        { :controller => 'journals', :action => 'new', :id => '1' }
25
      )
26
    assert_routing(
27
        { :method => 'get', :path => "/issues/changes" },
28
        { :controller => 'journals', :action => 'index' }
29
      )
30
    assert_routing(
31
        { :method => 'get', :path => "/journals/diff/1" },
32
        { :controller => 'journals', :action => 'diff', :id => '1' }
33
      )
34
    ["get", "post"].each do |method|
35
      assert_routing(
36
          { :method => method, :path => "/journals/edit/1" },
37
          { :controller => 'journals', :action => 'edit', :id => '1' }
38
        )
39
    end
40
  end
41
end
.svn/pristine/2d/2dbde491b7a799c190d49fd409652b2fbab77ca3.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 News < ActiveRecord::Base
19
  include Redmine::SafeAttributes
20
  belongs_to :project
21
  belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
22
  has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
23

  
24
  validates_presence_of :title, :description
25
  validates_length_of :title, :maximum => 60
26
  validates_length_of :summary, :maximum => 255
27

  
28
  acts_as_attachable :delete_permission => :manage_news
29
  acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project
30
  acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
31
  acts_as_activity_provider :find_options => {:include => [:project, :author]},
32
                            :author_key => :author_id
33
  acts_as_watchable
34

  
35
  after_create :add_author_as_watcher
36

  
37
  scope :visible, lambda {|*args|
38
    includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args))
39
  }
40

  
41
  safe_attributes 'title', 'summary', 'description'
42

  
43
  def visible?(user=User.current)
44
    !user.nil? && user.allowed_to?(:view_news, project)
45
  end
46

  
47
  # Returns true if the news can be commented by user
48
  def commentable?(user=User.current)
49
    user.allowed_to?(:comment_news, project)
50
  end
51

  
52
  def recipients
53
    project.users.select {|user| user.notify_about?(self)}.map(&:mail)
54
  end
55

  
56
  # returns latest news for projects visible by user
57
  def self.latest(user = User.current, count = 5)
58
    visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all
59
  end
60

  
61
  private
62

  
63
  def add_author_as_watcher
64
    Watcher.create(:watchable => self, :user => author)
65
  end
66
end
.svn/pristine/2d/2ddfd454c5c1fbd92838296a3b268aeb1e87e384.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 Redmine::ApiTest::GroupsTest < Redmine::ApiTest::Base
21
  fixtures :users, :groups_users
22

  
23
  def setup
24
    Setting.rest_api_enabled = '1'
25
  end
26

  
27
  context "GET /groups" do
28
    context ".xml" do
29
      should "require authentication" do
30
        get '/groups.xml'
31
        assert_response 401
32
      end
33

  
34
      should "return groups" do
35
        get '/groups.xml', {}, credentials('admin')
36
        assert_response :success
37
        assert_equal 'application/xml', response.content_type
38

  
39
        assert_select 'groups' do
40
          assert_select 'group' do
41
            assert_select 'name', :text => 'A Team'
42
            assert_select 'id', :text => '10'
43
          end
44
        end
45
      end
46
    end
47

  
48
    context ".json" do
49
      should "require authentication" do
50
        get '/groups.json'
51
        assert_response 401
52
      end
53

  
54
      should "return groups" do
55
        get '/groups.json', {}, credentials('admin')
56
        assert_response :success
57
        assert_equal 'application/json', response.content_type
58

  
59
        json = MultiJson.load(response.body)
60
        groups = json['groups']
61
        assert_kind_of Array, groups
62
        group = groups.detect {|g| g['name'] == 'A Team'}
63
        assert_not_nil group
64
        assert_equal({'id' => 10, 'name' => 'A Team'}, group)
65
      end
66
    end
67
  end
68

  
69
  context "GET /groups/:id" do
70
    context ".xml" do
71
      should "return the group with its users" do
72
        get '/groups/10.xml', {}, credentials('admin')
73
        assert_response :success
74
        assert_equal 'application/xml', response.content_type
75

  
76
        assert_select 'group' do
77
          assert_select 'name', :text => 'A Team'
78
          assert_select 'id', :text => '10'
79
        end
80
      end
81

  
82
      should "include users if requested" do
83
        get '/groups/10.xml?include=users', {}, credentials('admin')
84
        assert_response :success
85
        assert_equal 'application/xml', response.content_type
86

  
87
        assert_select 'group' do
88
          assert_select 'users' do
89
            assert_select 'user', Group.find(10).users.count
90
            assert_select 'user[id=8]'
91
          end
92
        end
93
      end
94

  
95
      should "include memberships if requested" do
96
        get '/groups/10.xml?include=memberships', {}, credentials('admin')
97
        assert_response :success
98
        assert_equal 'application/xml', response.content_type
99

  
100
        assert_select 'group' do
101
          assert_select 'memberships'
102
        end
103
      end
104
    end
105
  end
106

  
107
  context "POST /groups" do
108
    context "with valid parameters" do
109
      context ".xml" do
110
        should "create groups" do
111
          assert_difference('Group.count') do
112
            post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin')
113
            assert_response :created
114
            assert_equal 'application/xml', response.content_type
115
          end
116
  
117
          group = Group.order('id DESC').first
118
          assert_equal 'Test', group.name
119
          assert_equal [2, 3], group.users.map(&:id).sort
120

  
121
          assert_select 'group' do
122
            assert_select 'name', :text => 'Test'
123
          end
124
        end
125
      end
126
    end
127

  
128
    context "with invalid parameters" do
129
      context ".xml" do
130
        should "return errors" do
131
          assert_no_difference('Group.count') do
132
            post '/groups.xml', {:group => {:name => ''}}, credentials('admin')
133
          end
134
          assert_response :unprocessable_entity
135
          assert_equal 'application/xml', response.content_type
136

  
137
          assert_select 'errors' do
138
            assert_select 'error', :text => /Name can't be blank/
139
          end
140
        end
141
      end
142
    end
143
  end
144

  
145
  context "PUT /groups/:id" do
146
    context "with valid parameters" do
147
      context ".xml" do
148
        should "update the group" do
149
          put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin')
150
          assert_response :ok
151
          assert_equal '', @response.body
152
  
153
          group = Group.find(10)
154
          assert_equal 'New name', group.name
155
          assert_equal [2, 3], group.users.map(&:id).sort
156
        end
157
      end
158
    end
159

  
160
    context "with invalid parameters" do
161
      context ".xml" do
162
        should "return errors" do
163
          put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin')
164
          assert_response :unprocessable_entity
165
          assert_equal 'application/xml', response.content_type
166

  
167
          assert_select 'errors' do
168
            assert_select 'error', :text => /Name can't be blank/
169
          end
170
        end
171
      end
172
    end
173
  end
174

  
175
  context "DELETE /groups/:id" do
176
    context ".xml" do
177
      should "delete the group" do
178
        assert_difference 'Group.count', -1 do
179
          delete '/groups/10.xml', {}, credentials('admin')
180
          assert_response :ok
181
          assert_equal '', @response.body
182
        end
183
      end
184
    end
185
  end
186

  
187
  context "POST /groups/:id/users" do
188
    context ".xml" do
189
      should "add user to the group" do
190
        assert_difference 'Group.find(10).users.count' do
191
          post '/groups/10/users.xml', {:user_id => 5}, credentials('admin')
192
          assert_response :ok
193
          assert_equal '', @response.body
194
        end
195
        assert_include User.find(5), Group.find(10).users
196
      end
197
    end
198
  end
199

  
200
  context "DELETE /groups/:id/users/:user_id" do
201
    context ".xml" do
202
      should "remove user from the group" do
203
        assert_difference 'Group.find(10).users.count', -1 do
204
          delete '/groups/10/users/8.xml', {}, credentials('admin')
205
          assert_response :ok
206
          assert_equal '', @response.body
207
        end
208
        assert_not_include User.find(8), Group.find(10).users
209
      end
210
    end
211
  end
212
end
.svn/pristine/2d/2de3a87e3fe026c2ba56948a00c54c7c54909e85.svn-base
1
/* redMine - project management software
2
   Copyright (C) 2006-2008  Jean-Philippe Lang */
3

  
4
function checkAll (id, checked) {
5
	var els = Element.descendants(id);
6
	for (var i = 0; i < els.length; i++) {
7
    if (els[i].disabled==false) {
8
      els[i].checked = checked;
9
    }
10
	}
11
}
12

  
13
function toggleCheckboxesBySelector(selector) {
14
	boxes = $$(selector);
15
	var all_checked = true;
16
	for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
17
	for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
18
}
19

  
20
function setCheckboxesBySelector(checked, selector) {
21
  var boxes = $$(selector);
22
  boxes.each(function(ele) {
23
    ele.checked = checked;
24
  });
25
}
26

  
27
function showAndScrollTo(id, focus) {
28
	Element.show(id);
29
	if (focus!=null) { Form.Element.focus(focus); }
30
	Element.scrollTo(id);
31
}
32

  
33
function toggleRowGroup(el) {
34
	var tr = Element.up(el, 'tr');
35
	var n = Element.next(tr);
36
	tr.toggleClassName('open');
37
	while (n != undefined && !n.hasClassName('group')) {
38
		Element.toggle(n);
39
		n = Element.next(n);
40
	}
41
}
42

  
43
function collapseAllRowGroups(el) {
44
  var tbody = Element.up(el, 'tbody');
45
  tbody.childElements('tr').each(function(tr) {
46
    if (tr.hasClassName('group')) {
47
      tr.removeClassName('open');
48
    } else {
49
      tr.hide();
50
    }
51
  })
52
}
53

  
54
function expandAllRowGroups(el) {
55
  var tbody = Element.up(el, 'tbody');
56
  tbody.childElements('tr').each(function(tr) {
57
    if (tr.hasClassName('group')) {
58
      tr.addClassName('open');
59
    } else {
60
      tr.show();
61
    }
62
  })
63
}
64

  
65
function toggleAllRowGroups(el) {
66
	var tr = Element.up(el, 'tr');
67
  if (tr.hasClassName('open')) {
68
    collapseAllRowGroups(el);
69
  } else {
70
    expandAllRowGroups(el);
71
  }
72
}
73

  
74
function toggleFieldset(el) {
75
	var fieldset = Element.up(el, 'fieldset');
76
	fieldset.toggleClassName('collapsed');
77
	Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2});
78
}
79

  
80
function hideFieldset(el) {
81
	var fieldset = Element.up(el, 'fieldset');
82
	fieldset.toggleClassName('collapsed');
83
	fieldset.down('div').hide();
84
}
85

  
86
var fileFieldCount = 1;
87

  
88
function addFileField() {
89
  var fields = $('attachments_fields');
90
  if (fields.childElements().length >= 10) return false;
91
  fileFieldCount++;
92
  var s = new Element('span');
93
  s.update(fields.down('span').innerHTML);
94
  s.down('input.file').name = "attachments[" + fileFieldCount + "][file]";
95
  s.down('input.description').name = "attachments[" + fileFieldCount + "][description]";
96
  fields.appendChild(s);
97
}
98

  
99
function removeFileField(el) {
100
  var fields = $('attachments_fields');
101
	var s = Element.up(el, 'span');
102
	if (fields.childElements().length > 1) {
103
		s.remove();
104
	} else {
105
		s.update(s.innerHTML);
106
	}
107
}
108

  
109
function checkFileSize(el, maxSize, message) {
110
  var files = el.files;
111
  if (files) {
112
    for (var i=0; i<files.length; i++) {
113
      if (files[i].size > maxSize) {
114
        alert(message);
115
        el.value = "";
116
      }
117
    }
118
  }
119
}
120

  
121
function showTab(name) {
122
    var f = $$('div#content .tab-content');
123
	for(var i=0; i<f.length; i++){
124
		Element.hide(f[i]);
125
	}
126
    var f = $$('div.tabs a');
127
	for(var i=0; i<f.length; i++){
128
		Element.removeClassName(f[i], "selected");
129
	}
130
	Element.show('tab-content-' + name);
131
	Element.addClassName('tab-' + name, "selected");
132
	return false;
133
}
134

  
135
function moveTabRight(el) {
136
	var lis = Element.up(el, 'div.tabs').down('ul').childElements();
137
	var tabsWidth = 0;
138
	var i;
139
	for (i=0; i<lis.length; i++) {
140
		if (lis[i].visible()) {
141
			tabsWidth += lis[i].getWidth() + 6;
142
		}
143
	}
144
	if (tabsWidth < Element.up(el, 'div.tabs').getWidth() - 60) {
145
		return;
146
	}
147
	i=0;
148
	while (i<lis.length && !lis[i].visible()) {
149
		i++;
150
	}
151
	lis[i].hide();
152
}
153

  
154
function moveTabLeft(el) {
155
	var lis = Element.up(el, 'div.tabs').down('ul').childElements();
156
	var i = 0;
157
	while (i<lis.length && !lis[i].visible()) {
158
		i++;
159
	}
160
	if (i>0) {
161
		lis[i-1].show();
162
	}
163
}
164

  
165
function displayTabsButtons() {
166
	var lis;
167
	var tabsWidth = 0;
168
	var i;
169
	$$('div.tabs').each(function(el) {
170
		lis = el.down('ul').childElements();
171
		for (i=0; i<lis.length; i++) {
172
			if (lis[i].visible()) {
173
				tabsWidth += lis[i].getWidth() + 6;
174
			}
175
		}
176
		if ((tabsWidth < el.getWidth() - 60) && (lis[0].visible())) {
177
			el.down('div.tabs-buttons').hide();
178
		} else {
179
			el.down('div.tabs-buttons').show();
180
		}
181
	});
182
}
183

  
184
function setPredecessorFieldsVisibility() {
185
    relationType = $('relation_relation_type');
186
    if (relationType && (relationType.value == "precedes" || relationType.value == "follows")) {
187
        Element.show('predecessor_fields');
188
    } else {
189
        Element.hide('predecessor_fields');
190
    }
191
}
192

  
193
function promptToRemote(text, param, url) {
194
    value = prompt(text + ':');
195
    if (value) {
196
        new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
197
        return false;
198
    }
199
}
200

  
201
function showModal(id, width) {
202
  el = $(id);
203
	if (el == undefined || el.visible()) {return;}
204
  var h = $$('body')[0].getHeight();
205
  var d = document.createElement("div");
206
  d.id = 'modalbg';
207
  $('main').appendChild(d);
208
  $('modalbg').setStyle({ width: '100%', height: h + 'px' });
209
  $('modalbg').show();
210

  
211
  var pageWidth = document.viewport.getWidth();
212
	el.setStyle({'width': width});
213
	el.setStyle({'left': (((pageWidth - el.getWidth())/2  *100) / pageWidth) + '%'});
214
  el.addClassName('modal');
215
	el.show();
216

  
217
  var submit = el.down("input[type=submit]");
218
	if (submit) {
219
  	submit.focus();
220
  }
221
}
222

  
223
function hideModal(el) {
224
  var modal = Element.up(el, 'div.modal');
225
	if (modal) {
226
		modal.hide();
227
	}
228
	var bg = $('modalbg');
229
	if (bg) {
230
  	bg.remove();
231
  }
232
}
233

  
234
function collapseScmEntry(id) {
235
    var els = document.getElementsByClassName(id, 'browser');
236
	for (var i = 0; i < els.length; i++) {
237
	   if (els[i].hasClassName('open')) {
238
	       collapseScmEntry(els[i].id);
239
	   }
240
       Element.hide(els[i]);
241
    }
242
    $(id).removeClassName('open');
243
}
244

  
245
function expandScmEntry(id) {
246
    var els = document.getElementsByClassName(id, 'browser');
247
	for (var i = 0; i < els.length; i++) {
248
       Element.show(els[i]);
249
       if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
250
            expandScmEntry(els[i].id);
251
       }
252
    }
253
    $(id).addClassName('open');
254
}
255

  
256
function scmEntryClick(id) {
257
    el = $(id);
258
    if (el.hasClassName('open')) {
259
        collapseScmEntry(id);
260
        el.addClassName('collapsed');
261
        return false;
262
    } else if (el.hasClassName('loaded')) {
263
        expandScmEntry(id);
264
        el.removeClassName('collapsed');
265
        return false;
266
    }
267
    if (el.hasClassName('loading')) {
268
        return false;
269
    }
270
    el.addClassName('loading');
271
    return true;
272
}
273

  
274
function scmEntryLoaded(id) {
275
    Element.addClassName(id, 'open');
276
    Element.addClassName(id, 'loaded');
277
    Element.removeClassName(id, 'loading');
278
}
279

  
280
function randomKey(size) {
281
	var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
282
	var key = '';
283
	for (i = 0; i < size; i++) {
284
  	key += chars[Math.floor(Math.random() * chars.length)];
285
	}
286
	return key;
287
}
288

  
289
function observeParentIssueField(url) {
290
  new Ajax.Autocompleter('issue_parent_issue_id',
291
                         'parent_issue_candidates',
292
                         url,
293
                         { minChars: 3,
294
                           frequency: 0.5,
295
                           paramName: 'q',
296
                           method: 'get',
297
                           updateElement: function(value) {
298
                             document.getElementById('issue_parent_issue_id').value = value.id;
299
                           }});
300
}
301

  
302
function observeRelatedIssueField(url) {
303
  new Ajax.Autocompleter('relation_issue_to_id',
304
                         'related_issue_candidates',
305
                         url,
306
                         { minChars: 3,
307
                           frequency: 0.5,
308
                           paramName: 'q',
309
                           method: 'get',
310
                           updateElement: function(value) {
311
                             document.getElementById('relation_issue_to_id').value = value.id;
312
                           },
313
                           parameters: 'scope=all'
314
                           });
315
}
316

  
317
function setVisible(id, visible) {
318
  var el = $(id);
319
  if (el) {if (visible) {el.show();} else {el.hide();}}
320
}
321

  
322
function observeProjectModules() {
323
  var f = function() {
324
    /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
325
    var c = ($('project_enabled_module_names_issue_tracking').checked == true);
326
    setVisible('project_trackers', c);
327
    setVisible('project_issue_custom_fields', c);
328
  };
329
  
330
  Event.observe(window, 'load', f);
331
  Event.observe('project_enabled_module_names_issue_tracking', 'change', f);
332
}
333

  
334
/*
335
 * Class used to warn user when leaving a page with unsaved textarea
336
 * Author: mathias.fischer@berlinonline.de
337
*/
338

  
339
var WarnLeavingUnsaved = Class.create({
340
	observedForms: false,
341
	observedElements: false,
342
	changedForms: false,
343
	message: null,
344
	
345
	initialize: function(message){
346
		this.observedForms = $$('form');
347
		this.observedElements =  $$('textarea');
348
		this.message = message;
349
		
350
		this.observedElements.each(this.observeChange.bind(this));
351
		this.observedForms.each(this.submitAction.bind(this));
352
		
353
		window.onbeforeunload = this.unload.bind(this);
354
	},
355
	
356
	unload: function(){
357
		this.observedElements.each(function(el) {el.blur();})
358
		if(this.changedForms)
359
      return this.message;
360
	},
361
	
362
	setChanged: function(){
363
    this.changedForms = true;
364
	},
365
	
366
	setUnchanged: function(){
367
    this.changedForms = false;
368
	},
369
	
370
	observeChange: function(element){
371
    element.observe('change',this.setChanged.bindAsEventListener(this));
372
	},
373
	
374
	submitAction: function(element){
375
    element.observe('submit',this.setUnchanged.bindAsEventListener(this));
376
	}
377
});
378

  
379
/* 
380
 * 1 - registers a callback which copies the csrf token into the
381
 * X-CSRF-Token header with each ajax request.  Necessary to 
382
 * work with rails applications which have fixed
383
 * CVE-2011-0447
384
 * 2 - shows and hides ajax indicator
385
 */
386
Ajax.Responders.register({
387
    onCreate: function(request){
388
        var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
389

  
390
        if (csrf_meta_tag) {
391
            var header = 'X-CSRF-Token',
392
                token = csrf_meta_tag.readAttribute('content');
393

  
394
            if (!request.options.requestHeaders) {
395
              request.options.requestHeaders = {};
396
            }
397
            request.options.requestHeaders[header] = token;
398
          }
399

  
400
        if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
401
            Element.show('ajax-indicator');
402
        }
403
    },
404
    onComplete: function(){
405
        if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
406
            Element.hide('ajax-indicator');
407
        }
408
    }
409
});
410

  
411
function hideOnLoad() {
412
  $$('.hol').each(function(el) {
413
  	el.hide();
414
	});
415
}
416

  
417
Event.observe(window, 'load', hideOnLoad);

Also available in: Unified diff