Files
sanasto-wiki/test/controllers/invitations_controller_test.rb

396 lines
10 KiB
Ruby

require "test_helper"
class InvitationsControllerTest < ActionDispatch::IntegrationTest
test "should show invitation acceptance page with valid token" do
user = users(:pending_invitation)
get invitation_path(user.invitation_token)
assert_response :success
assert_select "h1", "Accept Invitation"
assert_select "input[type=password]", count: 2
end
test "should redirect with invalid token" do
get invitation_path("invalid_token")
assert_redirected_to root_path
assert_equal "Invalid or expired invitation link.", flash[:alert]
end
test "should redirect with expired token" do
user = users(:pending_invitation)
user.update(invitation_sent_at: 15.days.ago)
get invitation_path(user.invitation_token)
assert_redirected_to root_path
assert_equal "Invalid or expired invitation link.", flash[:alert]
end
test "should accept invitation with valid password" do
user = users(:pending_invitation)
patch accept_invitation_path(user.invitation_token), params: {
user: {
password: "securepassword123",
password_confirmation: "securepassword123"
}
}
user.reload
assert_not_nil user.invitation_accepted_at
assert_nil user.invitation_token
assert_equal user.id, session[:user_id]
assert_redirected_to root_path
end
test "should not accept invitation with short password" do
user = users(:pending_invitation)
patch accept_invitation_path(user.invitation_token), params: {
user: {
password: "short",
password_confirmation: "short"
}
}
user.reload
assert_nil user.invitation_accepted_at
assert_not_nil user.invitation_token
assert_response :unprocessable_entity
end
test "should not accept invitation with mismatched passwords" do
user = users(:pending_invitation)
patch accept_invitation_path(user.invitation_token), params: {
user: {
password: "securepassword123",
password_confirmation: "differentpassword"
}
}
user.reload
assert_nil user.invitation_accepted_at
assert_response :unprocessable_entity
end
test "should not accept expired invitation" do
user = users(:pending_invitation)
user.update(invitation_sent_at: 15.days.ago)
patch accept_invitation_path(user.invitation_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 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