96.99% test coverage
This commit is contained in:
@@ -90,4 +90,306 @@ class InvitationsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to root_path
|
||||
assert_equal "Invalid or expired invitation link.", flash[:alert]
|
||||
end
|
||||
|
||||
test "should redirect admin to dashboard after accepting invitation" do
|
||||
inviter = users(:admin_user)
|
||||
pending_admin = User.create!(
|
||||
email: "pending-admin@example.com",
|
||||
name: "Pending Admin",
|
||||
role: :admin,
|
||||
primary_language: "en",
|
||||
invitation_token: "pending_admin_token",
|
||||
invitation_sent_at: 1.day.ago,
|
||||
invited_by: inviter,
|
||||
password: "password123456"
|
||||
)
|
||||
|
||||
patch accept_invitation_path(pending_admin.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
pending_admin.reload
|
||||
assert_not_nil pending_admin.invitation_accepted_at
|
||||
assert_nil pending_admin.invitation_token
|
||||
assert_equal pending_admin.id, session[:user_id]
|
||||
assert_redirected_to admin_root_path
|
||||
end
|
||||
|
||||
# Entry activation tests
|
||||
test "should activate approved entries on invitation acceptance" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
# Create approved entry for this user
|
||||
approved_entry = Entry.create!(
|
||||
category: :word,
|
||||
fi: "Test word",
|
||||
status: :approved,
|
||||
requested_by: user
|
||||
)
|
||||
|
||||
# Create requested entry (should not be activated)
|
||||
requested_entry = Entry.create!(
|
||||
category: :word,
|
||||
fi: "Requested word",
|
||||
status: :requested,
|
||||
requested_by: user
|
||||
)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
approved_entry.reload
|
||||
requested_entry.reload
|
||||
|
||||
assert_equal "active", approved_entry.status
|
||||
assert_equal "requested", requested_entry.status
|
||||
end
|
||||
|
||||
test "should activate multiple approved entries on invitation acceptance" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
# Create multiple approved entries
|
||||
entries = 3.times.map do |i|
|
||||
Entry.create!(
|
||||
category: :word,
|
||||
fi: "Word #{i}",
|
||||
status: :approved,
|
||||
requested_by: user
|
||||
)
|
||||
end
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
entries.each do |entry|
|
||||
entry.reload
|
||||
assert_equal "active", entry.status
|
||||
end
|
||||
end
|
||||
|
||||
test "should not activate entries for other users" do
|
||||
user = users(:pending_invitation)
|
||||
other_user = users(:admin_user)
|
||||
|
||||
# Create approved entry for another user
|
||||
other_entry = Entry.create!(
|
||||
category: :word,
|
||||
fi: "Other user word",
|
||||
status: :approved,
|
||||
requested_by: other_user
|
||||
)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
other_entry.reload
|
||||
assert_equal "approved", other_entry.status
|
||||
end
|
||||
|
||||
test "should handle invitation acceptance with no entries" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
# Ensure the user has no entries
|
||||
Entry.where(requested_by: user).delete_all
|
||||
assert_equal 0, Entry.where(requested_by: user).count
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
user.reload
|
||||
assert_not_nil user.invitation_accepted_at
|
||||
end
|
||||
|
||||
# Security tests
|
||||
test "should only permit password parameters" do
|
||||
user = users(:pending_invitation)
|
||||
original_email = user.email
|
||||
original_name = user.name
|
||||
original_role = user.role
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123",
|
||||
email: "hacker@example.com",
|
||||
name: "Hacker Name",
|
||||
role: "admin"
|
||||
}
|
||||
}
|
||||
|
||||
user.reload
|
||||
assert_equal original_email, user.email
|
||||
assert_equal original_name, user.name
|
||||
assert_equal original_role, user.role
|
||||
end
|
||||
|
||||
test "should not log in user when password validation fails" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "short",
|
||||
password_confirmation: "short"
|
||||
}
|
||||
}
|
||||
|
||||
assert_nil session[:user_id]
|
||||
end
|
||||
|
||||
# Edge cases
|
||||
test "should handle already accepted invitation" do
|
||||
user = users(:pending_invitation)
|
||||
user.update!(invitation_accepted_at: Time.current, invitation_token: nil)
|
||||
|
||||
patch accept_invitation_path("some_token"), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert_equal "Invalid or expired invitation link.", flash[:alert]
|
||||
end
|
||||
|
||||
test "should set invitation_accepted_at timestamp" do
|
||||
user = users(:pending_invitation)
|
||||
assert_nil user.invitation_accepted_at
|
||||
|
||||
freeze_time do
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
user.reload
|
||||
assert_in_delta Time.current.to_i, user.invitation_accepted_at.to_i, 2
|
||||
end
|
||||
end
|
||||
|
||||
test "should clear invitation token after acceptance" do
|
||||
user = users(:pending_invitation)
|
||||
token = user.invitation_token
|
||||
assert_not_nil token
|
||||
|
||||
patch accept_invitation_path(token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
user.reload
|
||||
assert_nil user.invitation_token
|
||||
end
|
||||
|
||||
test "should require password to be present" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: nil,
|
||||
password_confirmation: nil
|
||||
}
|
||||
}
|
||||
|
||||
# has_secure_password validates password presence when setting password
|
||||
user.reload
|
||||
assert_nil user.invitation_accepted_at
|
||||
end
|
||||
|
||||
test "should show validation errors for short password" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "short",
|
||||
password_confirmation: "short"
|
||||
}
|
||||
}
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_select "div.text-red-700", text: /too short/i
|
||||
end
|
||||
|
||||
test "should show validation errors for mismatched passwords" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "differentpassword"
|
||||
}
|
||||
}
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_select "div.text-red-700", text: /confirmation/i
|
||||
end
|
||||
|
||||
test "should authenticate with new password after acceptance" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "mynewpassword123",
|
||||
password_confirmation: "mynewpassword123"
|
||||
}
|
||||
}
|
||||
|
||||
user.reload
|
||||
assert user.authenticate("mynewpassword123")
|
||||
assert_not user.authenticate("oldpassword")
|
||||
end
|
||||
|
||||
test "should log in user immediately after acceptance" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
# First visit the invitation page to establish session
|
||||
get invitation_path(user.invitation_token)
|
||||
assert_nil session[:user_id]
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal user.id, session[:user_id]
|
||||
end
|
||||
|
||||
test "should show welcome message with user name" do
|
||||
user = users(:pending_invitation)
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
assert_match /Welcome to Sanasto Wiki, #{user.name}!/, flash[:notice]
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user