96.99% test coverage
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
require "test_helper"
|
||||
|
||||
class AuthenticationFlowTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user = users(:admin_user)
|
||||
@user.update!(invitation_accepted_at: Time.current)
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
test "user can sign in with valid credentials" do
|
||||
get login_path
|
||||
assert_response :success
|
||||
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_redirected_to admin_root_path
|
||||
follow_redirect!
|
||||
assert_response :success
|
||||
assert_equal @user.id, session[:user_id]
|
||||
end
|
||||
|
||||
test "user cannot sign in with invalid password" do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "wrongpassword"
|
||||
}
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_nil session[:user_id]
|
||||
assert_select "div[role='alert']", text: /invalid email or password/i
|
||||
end
|
||||
|
||||
test "user cannot sign in with non-existent email" do
|
||||
post login_path, params: {
|
||||
email: "nonexistent@example.com",
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_nil session[:user_id]
|
||||
end
|
||||
|
||||
test "pending user cannot sign in" do
|
||||
pending_user = users(:pending_invitation)
|
||||
|
||||
post login_path, params: {
|
||||
email: pending_user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_nil session[:user_id]
|
||||
assert_select "div[role='alert']", text: /pending/i
|
||||
end
|
||||
|
||||
test "user can sign out" do
|
||||
# Sign in first
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
assert_equal @user.id, session[:user_id]
|
||||
|
||||
# Sign out
|
||||
delete logout_path
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert_nil session[:user_id]
|
||||
end
|
||||
|
||||
test "session persists across requests" do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_equal @user.id, session[:user_id]
|
||||
|
||||
get root_path
|
||||
assert_equal @user.id, session[:user_id]
|
||||
|
||||
get entries_path
|
||||
assert_equal @user.id, session[:user_id]
|
||||
end
|
||||
|
||||
test "remember me creates cookie" do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456",
|
||||
remember_me: "1"
|
||||
}
|
||||
|
||||
assert_not_nil cookies[:remember_token]
|
||||
@user.reload
|
||||
assert_not_nil @user.remember_token
|
||||
assert_not_nil @user.remember_created_at
|
||||
end
|
||||
|
||||
test "remember me cookie logs user in automatically" do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456",
|
||||
remember_me: "1"
|
||||
}
|
||||
remember_cookie = cookies[:remember_token]
|
||||
assert_not_nil remember_cookie
|
||||
|
||||
# Clear session
|
||||
reset!
|
||||
|
||||
# Make request with remember cookie
|
||||
cookies[:remember_token] = remember_cookie
|
||||
get root_path
|
||||
|
||||
assert_equal @user.id, session[:user_id]
|
||||
end
|
||||
|
||||
test "logout clears remember me cookie" do
|
||||
# Sign in with remember me
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456",
|
||||
remember_me: "1"
|
||||
}
|
||||
|
||||
assert_not_nil cookies[:remember_token]
|
||||
|
||||
# Sign out
|
||||
delete logout_path
|
||||
|
||||
remember_cookie = cookies[:remember_token]
|
||||
assert remember_cookie.nil? || remember_cookie.empty?
|
||||
@user.reload
|
||||
assert_nil @user.remember_token
|
||||
end
|
||||
|
||||
test "rate limiting prevents brute force" do
|
||||
max_attempts = 5
|
||||
responses = []
|
||||
|
||||
(max_attempts + 1).times do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "wrongpassword"
|
||||
}
|
||||
responses << response.status
|
||||
end
|
||||
|
||||
assert responses.first(max_attempts).all? { |status| status == 422 },
|
||||
"Expected first #{max_attempts} responses to be 422, got #{responses.first(max_attempts)}"
|
||||
|
||||
if Rails.cache.is_a?(ActiveSupport::Cache::NullStore)
|
||||
assert responses.last == 422,
|
||||
"Expected last response to be 422 with null cache store, got #{responses.last}"
|
||||
else
|
||||
assert responses.last == 429, "Expected last response to be 429, got #{responses.last}"
|
||||
end
|
||||
assert_select "div[role='alert']", text: /too many/i
|
||||
end
|
||||
|
||||
test "successful login resets rate limit" do
|
||||
# Fail a few times
|
||||
3.times do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "wrongpassword"
|
||||
}
|
||||
end
|
||||
|
||||
# Then succeed
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_redirected_to admin_root_path
|
||||
|
||||
# Should be able to login again immediately
|
||||
delete logout_path
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_redirected_to admin_root_path
|
||||
end
|
||||
|
||||
test "session timeout logs user out after inactivity" do
|
||||
# Sign in
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
assert_equal @user.id, session[:user_id]
|
||||
|
||||
# Simulate time passing
|
||||
travel 4.days do
|
||||
get root_path
|
||||
assert_redirected_to login_path
|
||||
assert_match /expired/i, flash[:alert]
|
||||
assert_nil session[:user_id]
|
||||
end
|
||||
end
|
||||
|
||||
test "remember me prevents session timeout" do
|
||||
# Sign in with remember me
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456",
|
||||
remember_me: "1"
|
||||
}
|
||||
|
||||
remember_cookie = cookies[:remember_token]
|
||||
|
||||
# Simulate time passing (but within remember me period)
|
||||
travel 5.days do
|
||||
cookies[:remember_token] = remember_cookie
|
||||
get root_path
|
||||
assert_response :success
|
||||
assert_equal @user.id, session[:user_id]
|
||||
end
|
||||
end
|
||||
|
||||
test "password reset flow completes successfully" do
|
||||
# Request reset
|
||||
post password_resets_path, params: { email: @user.email }
|
||||
assert_redirected_to login_path
|
||||
|
||||
@user.reload
|
||||
assert_not_nil @user.reset_password_token
|
||||
|
||||
# Visit reset form
|
||||
get edit_password_reset_path(@user.reset_password_token)
|
||||
assert_response :success
|
||||
|
||||
# Submit new password
|
||||
patch password_reset_path(@user.reset_password_token), params: {
|
||||
password: "newpassword12345",
|
||||
password_confirmation: "newpassword12345"
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert_equal @user.id, session[:user_id]
|
||||
|
||||
@user.reload
|
||||
assert @user.authenticate("newpassword12345")
|
||||
assert_nil @user.reset_password_token
|
||||
end
|
||||
|
||||
test "invitation acceptance flow" do
|
||||
pending_user = users(:pending_invitation)
|
||||
|
||||
# Visit invitation
|
||||
get invitation_path(pending_user.invitation_token)
|
||||
assert_response :success
|
||||
|
||||
# Accept invitation
|
||||
patch accept_invitation_path(pending_user.invitation_token), params: {
|
||||
user: {
|
||||
password: "newpassword12345",
|
||||
password_confirmation: "newpassword12345"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert_equal pending_user.id, session[:user_id]
|
||||
|
||||
pending_user.reload
|
||||
assert_not_nil pending_user.invitation_accepted_at
|
||||
assert pending_user.authenticate("newpassword12345")
|
||||
end
|
||||
|
||||
test "expired invitation cannot be accepted" do
|
||||
pending_user = users(:pending_invitation)
|
||||
pending_user.update!(invitation_sent_at: 15.days.ago)
|
||||
|
||||
get invitation_path(pending_user.invitation_token)
|
||||
assert_redirected_to root_path
|
||||
assert_match /expired/i, flash[:alert]
|
||||
end
|
||||
|
||||
test "admin user redirects to admin dashboard after login" do
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_redirected_to admin_root_path
|
||||
end
|
||||
|
||||
test "contributor redirects to root after login" do
|
||||
contributor = users(:contributor_user)
|
||||
contributor.update!(invitation_accepted_at: Time.current)
|
||||
|
||||
post login_path, params: {
|
||||
email: contributor.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
end
|
||||
|
||||
test "already logged in user redirects from login page" do
|
||||
# Sign in first
|
||||
post login_path, params: {
|
||||
email: @user.email,
|
||||
password: "password123456"
|
||||
}
|
||||
|
||||
# Try to visit login page again
|
||||
get login_path
|
||||
assert_redirected_to admin_root_path
|
||||
end
|
||||
end
|
||||
@@ -127,7 +127,7 @@ class EntryRequestFlowTest < ActionDispatch::IntegrationTest
|
||||
assert_response :success
|
||||
|
||||
# Active entry should be counted
|
||||
assert_match /#{Entry.active_entries.count}/, response.body
|
||||
assert_select "div", text: "#{Entry.active_entries.count} entries"
|
||||
|
||||
# Verify counts exclude requested/approved entries
|
||||
total_entries = Entry.count
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
require "test_helper"
|
||||
require "benchmark"
|
||||
|
||||
class SearchPerformanceTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
# Create a substantial number of test entries for performance testing
|
||||
@test_entries = []
|
||||
50.times do |i|
|
||||
@test_entries << Entry.create!(
|
||||
fi: "Testi sana #{i}",
|
||||
en: "Test word #{i}",
|
||||
sv: "Test ord #{i}",
|
||||
category: :word,
|
||||
status: :active
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
teardown do
|
||||
# Clean up test entries
|
||||
Entry.where(id: @test_entries.map(&:id)).delete_all
|
||||
end
|
||||
|
||||
test "full text search completes in reasonable time" do
|
||||
measure_time("Full text search") do
|
||||
get entries_path, params: { q: "test" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "language-specific search is performant" do
|
||||
measure_time("Language-specific search") do
|
||||
get entries_path, params: { q: "test", language: "en" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "alphabetical browsing is performant" do
|
||||
measure_time("Alphabetical browsing") do
|
||||
get entries_path, params: { language: "fi", starts_with: "t" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "category filtering is performant" do
|
||||
measure_time("Category filtering") do
|
||||
get entries_path, params: { category: "word" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "combined filters are performant" do
|
||||
measure_time("Combined filters") do
|
||||
get entries_path, params: { q: "test", language: "fi", category: "word" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "pagination does not degrade performance" do
|
||||
measure_time("Pagination") do
|
||||
get entries_path, params: { page: 2 }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "entry show page loads quickly" do
|
||||
entry = @test_entries.first
|
||||
|
||||
measure_time("Entry show page") do
|
||||
get entry_path(entry)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "XLSX download handles large datasets" do
|
||||
measure_time("XLSX download") do
|
||||
get download_entries_path(format: :xlsx)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "statistics calculation is performant" do
|
||||
measure_time("Statistics calculation") do
|
||||
get entries_path
|
||||
assert_response :success
|
||||
assert_select "div", text: /entries/i
|
||||
assert_select "div", text: /% complete/i
|
||||
end
|
||||
end
|
||||
|
||||
test "search with no results is fast" do
|
||||
measure_time("No results search") do
|
||||
get entries_path, params: { q: "nonexistentword12345xyz" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "multiple sequential searches maintain performance" do
|
||||
searches = [ "test", "sana", "word", "ord" ]
|
||||
|
||||
total_time = Benchmark.measure do
|
||||
searches.each do |query|
|
||||
get entries_path, params: { q: query }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
assert total_time.real < 2.0, "Multiple searches took too long: #{total_time.real}s"
|
||||
end
|
||||
|
||||
test "turbo stream responses are performant" do
|
||||
measure_time("Turbo stream response") do
|
||||
get entries_path, as: :turbo_stream, params: { q: "test" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def measure_time(description, max_time_ms: 500)
|
||||
time = Benchmark.measure do
|
||||
yield
|
||||
end
|
||||
|
||||
time_ms = (time.real * 1000).round(2)
|
||||
assert time_ms < max_time_ms,
|
||||
"#{description} took too long: #{time_ms}ms (max: #{max_time_ms}ms)"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user