396 lines
10 KiB
Ruby
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
|