Commit a4f117fe authored by Holger Just's avatar Holger Just

Merge branch 'release-v2.5.0' into stable

parents ee543489 7104a245
......@@ -18,6 +18,10 @@ group :test do
platforms :mri_19, :mingw_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
end
group :ldap do
gem "net-ldap", '~> 0.2.2'
end
group :openid do
gem "ruby-openid", '~> 2.1.4', :require => 'openid'
end
......
......@@ -16,8 +16,8 @@ class UsersController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => :show
before_filter :find_user, :only => [:show, :edit, :update, :edit_membership, :destroy_membership]
accept_key_auth :index, :show, :create, :update
before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
accept_key_auth :index, :show, :create, :update, :destroy
include SortHelper
include CustomFieldsHelper
......@@ -178,6 +178,24 @@ class UsersController < ApplicationController
redirect_to :controller => 'users', :action => 'edit', :id => @user
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
# Only allow to delete users with STATUS_REGISTERED for now
# It is assumed that these users are not yet references in any way
# from other objects.
return render_403 unless @user.deletable?
@user.destroy
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_delete)
redirect_back_or_default(:action => 'index')
}
format.api { head :ok }
end
end
def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post?
......
......@@ -413,7 +413,7 @@ module ApplicationHelper
title = []
title << h(@project.name) if @project
title += @html_title if @html_title
title << Setting.app_title
title << h(Setting.app_title)
title.select {|t| !t.blank? }.join(' - ')
else
@html_title ||= []
......
......@@ -37,13 +37,26 @@ module UsersHelper
def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
links = []
if user.locked?
link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
links << link_to(l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock')
elsif user.registered?
link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
links << link_to(l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock')
elsif user != User.current
link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
links << link_to(l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock')
end
if user.deletable?
links << link_to(
l(:button_delete), {:controller => 'users', :action => 'destroy', :id => user},
:method => :delete,
:confirm => l(:text_are_you_sure),
:title => l(:button_delete),
:class => 'icon icon-del'
)
end
links.join(" ")
end
def user_settings_tabs
......
......@@ -108,7 +108,7 @@ class Journal < ActiveRecord::Base
## => Try the journaled object with the same method and arguments
## => On error, call super
def method_missing(method, *args, &block)
return super if attributes[method.to_s]
return super if respond_to?(method) || attributes[method.to_s]
journaled.send(method, *args, &block)
rescue NoMethodError => e
e.name == method ? super : raise(e)
......
......@@ -396,8 +396,8 @@ class Mailer < ActionMailer::Base
# if he doesn't want to receive notifications about what he does
@author ||= User.current
if @author.pref[:no_self_notified]
recipients.delete(@author.mail) if recipients
cc.delete(@author.mail) if cc
recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present?
cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present?
end
notified_users = [recipients, cc].flatten.compact.uniq
......
......@@ -70,6 +70,7 @@ class User < Principal
validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
validates_inclusion_of :status, :in => [STATUS_ANONYMOUS, STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
named_scope :in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
......@@ -207,6 +208,11 @@ class User < Principal
update_attribute(:status, STATUS_LOCKED)
end
def deletable?
registered? && last_login_on.nil?
end
# Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password)
if auth_source_id.present?
......@@ -526,6 +532,24 @@ class User < Principal
if !password.nil? && password.size < Setting.password_min_length.to_i
errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
end
# Status
if !new_record? && status_changed?
case status_was
when nil
# initial setting is always save
true
when STATUS_ANONYMOUS
# never allow a state change of the anonymous user
false
when STATUS_REGISTERED
[STATUS_ACTIVE, STATUS_LOCKED].include? status
when STATUS_ACTIVE
[STATUS_LOCKED].include? status
when STATUS_LOCKED
[STATUS_ACTIVE].include? status
end || errors.add(:status, :inclusion)
end
end
private
......
......@@ -104,7 +104,12 @@ class WikiContent < ActiveRecord::Base
def text
@text ||= case changes["compression"]
when "gzip"
Zlib::Inflate.inflate(changes["data"])
data = Zlib::Inflate.inflate(changes["data"])
if data.respond_to? :force_encoding
data.force_encoding("UTF-8")
else
data
end
else
# uncompressed data
changes["data"]
......
......@@ -15,7 +15,7 @@
<%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
</fieldset>
<% end %>
<% if authorize_for('timelog', 'edit') %>
<% if User.current.allowed_to?(:log_time, @project) %>
<fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
<div class="splitcontentleft">
......@@ -26,7 +26,7 @@
</div>
<p><%= time_entry.text_field :comments, :size => 60 %></p>
<% @time_entry.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<% end %>
<% end %>
</fieldset>
......
......@@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><%=h html_title %></title>
<title><%= html_title %></title>
<meta name="description" content="<%= Redmine::Info.app_name %>" />
<meta name="keywords" content="issue,bug,tracker" />
<%= csrf_meta_tag %>
......
......@@ -8,10 +8,13 @@
<%= link_to(l(:label_overall_spent_time), { :controller => 'time_entries' }) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %>
<%= link_to(l(:label_news_view_all), { :controller => 'news' }) + ' |' if User.current.allowed_to?(:view_news, nil, :global => true) %>
<%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%>
<%= call_hook(:view_projects_show_contextual) %>
</div>
<h2><%=l(:label_project_plural)%></h2>
<%= call_hook(:view_projects_show_top) %>
<%= render_project_hierarchy(@projects)%>
<% if User.current.logged? %>
......
......@@ -97,11 +97,11 @@ Event.observe(document,"dom:loaded", apply_filters_observer);
</select>
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
<% when :date, :date_past %>
<%= text_field_tag "v[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
<%= text_field_tag "v[#{field}][]", query.values_for(field).try(:first), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
<% when :string, :text %>
<%= text_field_tag "v[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
<%= text_field_tag "v[#{field}][]", query.values_for(field).try(:first), :id => "values_#{field}", :size => 30, :class => "select-small" %>
<% when :integer %>
<%= text_field_tag "v[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %>
<%= text_field_tag "v[#{field}][]", query.values_for(field).try(:first), :id => "values_#{field}", :size => 3, :class => "select-small" %>
<% end %>
</div>
<script type="text/javascript">toggle_filter('<%= field %>');</script>
......
......@@ -960,26 +960,26 @@ bg:
field_effective_date: Дата
text_default_encoding: "По подразбиране: UTF-8"
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
label_notify_member_plural: Email issue updates
label_notify_member_plural: Изпращане на e-mail при промени в задачите
label_path_encoding: Кодиране на пътищата
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
label_diff: diff
setting_issue_startdate_is_adddate: Use current date as start date for new issues
description_search: Searchfield
description_user_mail_notification: Mail notification settings
description_date_range_list: Choose range from list
description_date_to: Enter end date
description_query_sort_criteria_attribute: Sort attribute
description_message_content: Message content
description_wiki_subpages_reassign: Choose new parent page
description_available_columns: Available Columns
description_selected_columns: Selected Columns
description_date_range_interval: Choose range by selecting start and end date
description_project_scope: Search scope
description_issue_category_reassign: Choose issue category
description_query_sort_criteria_direction: Sort direction
description_notes: Notes
description_filter: Filter
description_choose_project: Projects
description_date_from: Enter start date
label_deleted_custom_field: (deleted custom field)
setting_issue_startdate_is_adddate: Използване на текущата дата като начална дата за нови задачи
description_search: Търсене
description_user_mail_notification: Конфигурация известията по пощата
description_date_range_list: Изберете диапазон от списъка
description_date_to: Въведете крайна дата
description_query_sort_criteria_attribute: Атрибут на сортиране
description_message_content: Съдържание на съобщението
description_wiki_subpages_reassign: Изберете нова родителска страница
description_available_columns: Налични колони
description_selected_columns: Избрани колони
description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати
description_project_scope: Обхват на търсенето
description_issue_category_reassign: Изберете категория
description_query_sort_criteria_direction: Посока на сортиране
description_notes: Бележки
description_filter: Филтър
description_choose_project: Проекти
description_date_from: Въведете начална дата
label_deleted_custom_field: (изтрито потребителско поле)
......@@ -19,9 +19,9 @@ rescue LoadError
raise "Could not load the bundler gem. Install it with `gem install bundler`."
end
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
"Run `gem install bundler` to upgrade."
if Gem::Version.new(Bundler::VERSION) < Gem::Version.new("1.0.6")
raise RuntimeError, "Your bundler version is too old. We require " +
"at least version 1.0.6. Run `gem install bundler` to upgrade."
end
begin
......@@ -29,6 +29,6 @@ begin
ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
Bundler.setup
rescue Bundler::GemNotFound
raise RuntimeError, "Bundler couldn't find some gems." +
raise RuntimeError, "Bundler couldn't find some gems. " +
"Did you run `bundle install`?"
end
......@@ -137,8 +137,7 @@ ActionController::Routing::Routes.draw do |map|
map.resources :users, :member => {
:edit_membership => :post,
:destroy_membership => :post
},
:except => [:destroy]
}
# For nice "roadmap" in the url for the index action
map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
......
This diff is collapsed.
......@@ -49,7 +49,7 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
PerlAccessHandler Apache::Authn::Redmine::access_handler
PerlAuthenHandler Apache::Authn::Redmine::authen_handler
## for mysql
RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
## for postgres
......@@ -227,31 +227,31 @@ my @directives = (
},
);
sub RedmineDSN {
sub RedmineDSN {
my ($self, $parms, $arg) = @_;
$self->{RedmineDSN} = $arg;
my $query = "SELECT
my $query = "SELECT
hashed_password, salt, auth_source_id, permissions
FROM members, projects, users, roles, member_roles
WHERE
WHERE
projects.id=members.project_id
AND member_roles.member_id=members.id
AND users.id=members.user_id
AND users.id=members.user_id
AND roles.id=member_roles.role_id
AND users.status=1
AND login=?
AND users.status=1
AND login=?
AND identifier=? ";
$self->{RedmineQuery} = trim($query);
}
sub RedmineDbUser { set_val('RedmineDbUser', @_); }
sub RedmineDbPass { set_val('RedmineDbPass', @_); }
sub RedmineDbWhereClause {
sub RedmineDbWhereClause {
my ($self, $parms, $arg) = @_;
$self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
}
sub RedmineCacheCredsMax {
sub RedmineCacheCredsMax {
my ($self, $parms, $arg) = @_;
if ($arg) {
$self->{RedmineCachePool} = APR::Pool->new;
......@@ -325,10 +325,10 @@ sub access_handler {
sub authen_handler {
my $r = shift;
my ($res, $redmine_pass) = $r->get_basic_auth_pw();
return $res unless $res == OK;
if (is_member($r->user, $redmine_pass, $r)) {
return OK;
} else {
......@@ -355,7 +355,7 @@ sub is_authentication_forced {
}
$sth->finish();
undef $sth;
$dbh->disconnect();
undef $dbh;
......@@ -365,7 +365,7 @@ sub is_authentication_forced {
sub is_public_project {
my $project_id = shift;
my $r = shift;
if (is_authentication_forced($r)) {
return 0;
}
......@@ -392,12 +392,12 @@ sub is_public_project {
sub anonymous_role_allows_browse_repository {
my $r = shift;
my $dbh = connect_database($r);
my $sth = $dbh->prepare(
"SELECT permissions FROM roles WHERE builtin = 2;"
);
$sth->execute();
my $ret = 0;
if (my @row = $sth->fetchrow_array) {
......@@ -409,7 +409,7 @@ sub anonymous_role_allows_browse_repository {
undef $sth;
$dbh->disconnect();
undef $dbh;
$ret;
}
......@@ -438,10 +438,12 @@ sub is_member {
my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
my $access_mode = request_is_read_only($r) ? "R" : "W";
my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
my $usrprojpass;
if ($cfg->{RedmineCacheCredsMax}) {
$usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
$usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
}
my $query = $cfg->{RedmineQuery};
......@@ -485,10 +487,10 @@ sub is_member {
if ($cfg->{RedmineCacheCredsMax} and $ret) {
if (defined $usrprojpass) {
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
} else {
if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
$cfg->{RedmineCacheCredsCount}++;
} else {
$cfg->{RedmineCacheCreds}->clear();
......@@ -502,7 +504,7 @@ sub is_member {
sub get_project_identifier {
my $r = shift;
my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
my $location = $r->location;
my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
......@@ -512,7 +514,7 @@ sub get_project_identifier {
sub connect_database {
my $r = shift;
my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
}
......
......@@ -21,5 +21,19 @@ module ChiliProject
Journal.included_modules.include?(Redmine::Acts::Journalized)
end
# Is any jQuery version available on all pages?
#
# This does not take modifications into account, that may be performed by
# plugins.
#
# Released: ChiliProject 2.5.0
def self.using_jquery?
false
end
# Catch-all to be overwritten be future compatibility checks.
def self.method_missing(method, *args)
method.to_s.ends_with?('?') ? false : super
end
end
end
......@@ -18,7 +18,7 @@ module ChiliProject
module VERSION #:nodoc:
MAJOR = 2
MINOR = 4
MINOR = 5
PATCH = 0
TINY = PATCH # Redmine compat
......
......@@ -100,10 +100,10 @@ Redmine::AccessControl.map do |map|
end
map.project_module :time_tracking do |map|
map.permission :log_time, {:timelog => [:new, :create, :edit, :update]}, :require => :loggedin
map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :member
map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy]}, :require => :member
map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy]}, :require => :loggedin
map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
end
......
......@@ -22,7 +22,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
super
end
(field_helpers - %w(radio_button hidden_field fields_for) + %w(date_select)).each do |selector|
(field_helpers.map(&:to_s) - %w(radio_button hidden_field fields_for) + %w(date_select)).each do |selector|
src = <<-END_SRC
def #{selector}(field, options = {})
label_for_field(field, options) + super
......
......@@ -781,6 +781,22 @@ class IssuesControllerTest < ActionController::TestCase
assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
end
def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
@request.session[:user_id] = 2
Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
get :edit, :id => 1
assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
end
def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
@request.session[:user_id] = 2
Role.find_by_name('Manager').remove_permission! :log_time
get :edit, :id => 1
assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
end
def test_update_edit_form
@request.session[:user_id] = 2
xhr :post, :new, :project_id => 1,
......
......@@ -111,6 +111,18 @@ class TimelogControllerTest < ActionController::TestCase
assert_equal 3, t.user_id
end
def test_create_without_log_time_permission_should_be_denied
@request.session[:user_id] = 2
Role.find_by_name('Manager').remove_permission! :log_time
post :create, :project_id => 1,
:time_entry => {:activity_id => '11',
:issue_id => '',
:spent_on => '2008-03-14',
:hours => '7.3'}
assert_response 403
end
def test_update
entry = TimeEntry.find(1)
assert_equal 1, entry.issue_id
......
......@@ -270,6 +270,31 @@ class UsersControllerTest < ActionController::TestCase
assert u.check_password?('newpass')
end
def test_destroy
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
u.login = 'death.row'
u.status = User::STATUS_REGISTERED
u.save!
delete :destroy, :id => u.id
assert_redirected_to :action => 'index'
# make sure that the user was actually destroyed
assert_raises(ActiveRecord::RecordNotFound) { u.reload }
end
def test_failing_destroy
u = User.new(:firstname => 'Surviving', :lastname => 'Patient', :mail => 'surviving.patient@example.com', :language => 'en')
u.login = 'surviving.patient'
u.status = User::STATUS_ACTIVE
u.save!
delete :destroy, :id => u.id
assert_response :forbidden
# make sure the user is still around
assert !u.reload.destroyed?
end
def test_edit_membership
post :edit_membership, :id => 2, :membership_id => 1,
:membership => { :role_ids => [2]}
......
......@@ -241,26 +241,52 @@ class ApiTest::UsersTest < ActionController::IntegrationTest
end
end
end
end
context "DELETE /users/2" do
context ".xml" do
should "not be allowed" do
assert_no_difference('User.count') do
delete '/users/2.xml'
end
context "DELETE /users/:temp:" do
context ".xml" do
should "delete the user" do
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
u.login = 'death.row'
u.status = User::STATUS_REGISTERED
u.save!
assert_difference('User.count',-1) do
delete "/users/#{u.id}.xml", {}, :authorization => credentials('admin')
end
assert_response :success
assert_nil User.find_by_id(u.id)
end
assert_response :method_not_allowed
should "not delete active user" do
assert_difference('User.count',0) do
delete "/users/2.xml", {}, :authorization => credentials('jsmith')
end
assert_response :forbidden
end
end
context ".json" do
should "not be allowed" do
assert_no_difference('User.count') do
delete '/users/2.json'
end
context ".json" do
should "delete the user" do
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
u.login = 'death.row'
u.status = User::STATUS_REGISTERED
u.save!
assert_difference('User.count',-1) do
delete "/users/#{u.id}.json", {}, :authorization => credentials('admin')
end
assert_response :success
assert_nil User.find_by_id(u.id)
end
assert_response :method_not_allowed
should "not delete active user" do
assert_difference('User.count',0) do
delete "/users/2.json", {}, :authorization => credentials('jsmith')
end
assert_response :forbidden
end
end
end
......
......@@ -60,4 +60,15 @@ class LayoutTest < ActionController::IntegrationTest
:attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}},
:parent => {:tag => 'head'}
end
test "page titles should be properly escaped" do
project = Project.generate(:name => "C&A")
with_settings :app_title => '<3' do
get "/projects/#{project.to_param}"
assert_select "title", /C&amp;A/
assert_select "title", /&lt;3/
end
end
end
......@@ -174,6 +174,14 @@ class UserTest < ActiveSupport::TestCase
assert_equal nil, user
end
def test_error_on_active_to_registered
user = User.try_to_login("jsmith", "jsmith")
assert_equal @jsmith, user
@jsmith.status = User::STATUS_REGISTERED
assert !@jsmith.save
end
context ".try_to_login" do
context "with good credentials" do
should "return the user" do
......
This diff is collapsed.