add entry requests, invite new users
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
# Entry Request System Test Coverage
|
||||
|
||||
## Test Files Created
|
||||
|
||||
### 1. Model Tests: `test/models/entry_request_test.rb`
|
||||
Tests for Entry model enhancements:
|
||||
- ✅ Validates that at least one translation is required
|
||||
- ✅ Entry status defaults to "active"
|
||||
- ✅ Scopes (requested, approved, active_entries) work correctly
|
||||
- ✅ requested_by association functions properly
|
||||
- ✅ Status transitions (requested → approved → active)
|
||||
- ✅ Blank translations are properly handled
|
||||
|
||||
**13 tests, 40 assertions**
|
||||
|
||||
### 2. Public Controller Tests: `test/controllers/requests_controller_test.rb`
|
||||
Tests for public entry request submission:
|
||||
- ✅ Shows new request form for anonymous users (with name/email fields)
|
||||
- ✅ Shows new request form for logged-in users (without name/email fields)
|
||||
- ✅ Creates entry request with valid data
|
||||
- ✅ Requires at least one translation
|
||||
- ✅ Redirects to login if email already exists with accepted invitation
|
||||
- ✅ Shows pending count for email with existing requests
|
||||
- ✅ Creates entry with single or multiple translations
|
||||
- ✅ Logged-in user can submit request without providing name/email
|
||||
- ✅ Does not modify existing user when they submit request
|
||||
- ✅ Reuses existing pending user without modifying them
|
||||
- ✅ Transaction rollback on validation failure
|
||||
|
||||
**11 tests, 78 assertions**
|
||||
|
||||
### 3. Admin Controller Tests: `test/controllers/admin/requests_controller_test.rb`
|
||||
Tests for admin request management:
|
||||
- ✅ Requires admin authentication
|
||||
- ✅ Shows requests index with requested and approved sections
|
||||
- ✅ Lists requested and approved entries
|
||||
- ✅ Shows request details
|
||||
- ✅ Shows edit form for requested entry
|
||||
- ✅ Updates entry details
|
||||
- ✅ Validates entry data on update
|
||||
- ✅ Approves request and sends invitation email
|
||||
- ✅ Rejects request and deletes entry/user
|
||||
- ✅ Preserves user if they have multiple entries
|
||||
- ✅ Blocks access for non-admin users (contributors, reviewers)
|
||||
|
||||
**14 tests, 85 assertions**
|
||||
|
||||
### 4. Integration Tests: `test/integration/entry_request_flow_test.rb`
|
||||
Full end-to-end flow tests:
|
||||
- ✅ Complete flow: request → admin approve → user accepts → entry active
|
||||
- ✅ Rejected request removes entry and user
|
||||
- ✅ Requested/approved entries not visible on public site
|
||||
- ✅ Multiple entries by same requester all activated on invitation acceptance
|
||||
- ✅ Admin can edit entry details before approval
|
||||
- ✅ Cannot submit request with existing user email
|
||||
|
||||
**6 tests**
|
||||
|
||||
## Fixtures Added
|
||||
|
||||
### Updated: `test/fixtures/entries.yml`
|
||||
- Added `status: 2` (active) to existing entries
|
||||
- Added `requested_entry` fixture (status: requested)
|
||||
- Added `approved_entry` fixture (status: approved)
|
||||
|
||||
### Updated: `test/fixtures/users.yml`
|
||||
- Added `requester_user` fixture (user without accepted invitation)
|
||||
|
||||
### 5. Mailer Tests: `test/mailers/invitation_mailer_test.rb`
|
||||
Invitation email tests including entry approval notifications:
|
||||
- ✅ Sends email with correct details
|
||||
- ✅ Includes invitation link and expiry date
|
||||
- ✅ Has both HTML and text parts
|
||||
- ✅ **With approved entry: includes entry details in email**
|
||||
- ✅ **With approved entry: shows correct message and formatting**
|
||||
- ✅ Without approved entry: uses standard invitation message
|
||||
|
||||
**7 tests, 38 assertions**
|
||||
|
||||
## Test Summary
|
||||
|
||||
**Total: 51 tests, 316 assertions**
|
||||
|
||||
All tests passing ✅
|
||||
|
||||
Full test suite: **131 tests, 566 assertions** ✅
|
||||
|
||||
## Running the Tests
|
||||
|
||||
Run all request-related tests:
|
||||
```bash
|
||||
bin/rails test test/models/entry_request_test.rb \
|
||||
test/controllers/requests_controller_test.rb \
|
||||
test/controllers/admin/requests_controller_test.rb \
|
||||
test/integration/entry_request_flow_test.rb
|
||||
```
|
||||
|
||||
Run individual test files:
|
||||
```bash
|
||||
bin/rails test test/models/entry_request_test.rb
|
||||
bin/rails test test/controllers/requests_controller_test.rb
|
||||
bin/rails test test/controllers/admin/requests_controller_test.rb
|
||||
bin/rails test test/integration/entry_request_flow_test.rb
|
||||
```
|
||||
|
||||
Run specific test:
|
||||
```bash
|
||||
bin/rails test test/integration/entry_request_flow_test.rb:3
|
||||
```
|
||||
|
||||
## Test Coverage Areas
|
||||
|
||||
### Public Request Flow
|
||||
- Form display and validation (different for logged-in vs anonymous users)
|
||||
- User and entry creation
|
||||
- Email duplicate detection for active accounts
|
||||
- Logged-in users submit without providing name/email
|
||||
- Existing users are never modified during request submission
|
||||
- Existing pending users are reused without modification
|
||||
- Transaction safety
|
||||
|
||||
### Admin Management Flow
|
||||
- Authentication and authorization
|
||||
- Request listing and filtering
|
||||
- Request details display
|
||||
- Entry editing before approval
|
||||
- Approval with invitation sending
|
||||
- Rejection with cleanup
|
||||
|
||||
### Integration Flow
|
||||
- Complete user journey from request to active entry
|
||||
- Entry visibility rules (requested/approved not shown publicly)
|
||||
- Multi-entry approval and activation
|
||||
- Admin workflow with editing
|
||||
|
||||
### Edge Cases
|
||||
- Validation failures with transaction rollback
|
||||
- User preservation when they have multiple entries
|
||||
- Expired invitations
|
||||
- Non-admin access attempts
|
||||
- Blank translations handling
|
||||
@@ -0,0 +1,184 @@
|
||||
require "test_helper"
|
||||
|
||||
class Admin::RequestsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@admin = users(:admin_user)
|
||||
@requested_entry = entries(:requested_entry)
|
||||
@approved_entry = entries(:approved_entry)
|
||||
login_as(@admin)
|
||||
end
|
||||
|
||||
test "should require admin authentication" do
|
||||
logout
|
||||
get admin_requests_path
|
||||
assert_redirected_to login_path
|
||||
end
|
||||
|
||||
test "should show requests index" do
|
||||
get admin_requests_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "h1", "Entry Requests"
|
||||
assert_select "h2", /Pending Review/
|
||||
assert_select "h2", /Approved/
|
||||
end
|
||||
|
||||
test "should list requested entries" do
|
||||
get admin_requests_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "td", text: @requested_entry.fi
|
||||
assert_select "a[href=?]", admin_request_path(@requested_entry)
|
||||
end
|
||||
|
||||
test "should list approved entries" do
|
||||
get admin_requests_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "td", text: @approved_entry.fi
|
||||
end
|
||||
|
||||
test "should show request details" do
|
||||
get admin_request_path(@requested_entry)
|
||||
|
||||
assert_response :success
|
||||
assert_select "h1", "Entry Request Details"
|
||||
assert_match @requested_entry.fi, response.body
|
||||
assert_match @requested_entry.en, response.body
|
||||
assert_match @requested_entry.requested_by.name, response.body
|
||||
end
|
||||
|
||||
test "should show edit form for requested entry" do
|
||||
get edit_admin_request_path(@requested_entry)
|
||||
|
||||
assert_response :success
|
||||
assert_select "h1", "Edit Entry Request"
|
||||
assert_select "form[action=?]", admin_request_path(@requested_entry)
|
||||
assert_select "input[name='entry[fi]'][value=?]", @requested_entry.fi
|
||||
end
|
||||
|
||||
test "should update entry details" do
|
||||
patch admin_request_path(@requested_entry), params: {
|
||||
entry: {
|
||||
category: "phrase",
|
||||
fi: "päivitetty sana",
|
||||
en: "updated word",
|
||||
notes: "Updated notes"
|
||||
}
|
||||
}
|
||||
|
||||
@requested_entry.reload
|
||||
assert_equal "phrase", @requested_entry.category
|
||||
assert_equal "päivitetty sana", @requested_entry.fi
|
||||
assert_equal "updated word", @requested_entry.en
|
||||
assert_equal "Updated notes", @requested_entry.notes
|
||||
assert_redirected_to admin_request_path(@requested_entry)
|
||||
assert_equal "Request updated successfully.", flash[:notice]
|
||||
end
|
||||
|
||||
test "should not update with invalid data" do
|
||||
patch admin_request_path(@requested_entry), params: {
|
||||
entry: {
|
||||
fi: "",
|
||||
en: "",
|
||||
sv: "",
|
||||
no: "",
|
||||
ru: "",
|
||||
de: ""
|
||||
}
|
||||
}
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
@requested_entry.reload
|
||||
assert_equal "testisana", @requested_entry.fi # Unchanged
|
||||
end
|
||||
|
||||
test "should approve request and send invitation" do
|
||||
user = @requested_entry.requested_by
|
||||
assert_nil user.invitation_token
|
||||
assert_nil user.invitation_sent_at
|
||||
|
||||
assert_enqueued_emails 1 do
|
||||
post approve_admin_request_path(@requested_entry)
|
||||
end
|
||||
|
||||
user.reload
|
||||
@requested_entry.reload
|
||||
|
||||
assert_equal "approved", @requested_entry.status
|
||||
assert_not_nil user.invitation_token
|
||||
assert_not_nil user.invitation_sent_at
|
||||
assert_equal @admin, user.invited_by
|
||||
assert_redirected_to admin_requests_path
|
||||
assert_match(/invitation sent/i, flash[:notice])
|
||||
end
|
||||
|
||||
test "should not approve already approved entry" do
|
||||
# Try to approve an already approved entry
|
||||
user = @approved_entry.requested_by
|
||||
|
||||
post approve_admin_request_path(@approved_entry)
|
||||
|
||||
@approved_entry.reload
|
||||
assert_equal "approved", @approved_entry.status
|
||||
end
|
||||
|
||||
test "should reject request and delete entry and user" do
|
||||
user = @requested_entry.requested_by
|
||||
entry_id = @requested_entry.id
|
||||
user_id = user.id
|
||||
|
||||
assert_difference("Entry.count", -1) do
|
||||
assert_difference("User.count", -1) do
|
||||
delete reject_admin_request_path(@requested_entry)
|
||||
end
|
||||
end
|
||||
|
||||
assert_not Entry.exists?(entry_id)
|
||||
assert_not User.exists?(user_id)
|
||||
assert_redirected_to admin_requests_path
|
||||
assert_match(/rejected and deleted/i, flash[:notice])
|
||||
end
|
||||
|
||||
test "should reject but not delete user with multiple entries" do
|
||||
user = @requested_entry.requested_by
|
||||
|
||||
# Create another entry for the same user
|
||||
another_entry = Entry.create!(
|
||||
category: :word,
|
||||
fi: "toinen sana",
|
||||
en: "another word",
|
||||
status: :requested,
|
||||
requested_by: user
|
||||
)
|
||||
|
||||
assert_difference("Entry.count", -1) do
|
||||
assert_no_difference("User.count") do
|
||||
delete reject_admin_request_path(@requested_entry)
|
||||
end
|
||||
end
|
||||
|
||||
assert User.exists?(user.id)
|
||||
assert Entry.exists?(another_entry.id)
|
||||
end
|
||||
|
||||
test "contributors should not access admin requests" do
|
||||
logout
|
||||
contributor = users(:contributor_user)
|
||||
login_as(contributor)
|
||||
|
||||
get admin_requests_path
|
||||
assert_redirected_to root_path
|
||||
assert_match(/administrator/i, flash[:alert])
|
||||
end
|
||||
|
||||
test "reviewers should not access admin requests" do
|
||||
logout
|
||||
reviewer = users(:reviewer_user)
|
||||
login_as(reviewer)
|
||||
|
||||
get admin_requests_path
|
||||
assert_redirected_to root_path
|
||||
assert_match(/administrator/i, flash[:alert])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,223 @@
|
||||
require "test_helper"
|
||||
|
||||
class RequestsControllerTest < ActionDispatch::IntegrationTest
|
||||
test "should show new request form for anonymous users" do
|
||||
get new_request_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "h1", "Request a New Entry"
|
||||
assert_select "form"
|
||||
assert_select "input[name='entry[name]']"
|
||||
assert_select "input[name='entry[email]']"
|
||||
assert_select "select[name='entry[category]']"
|
||||
end
|
||||
|
||||
test "should show new request form for logged-in users without name/email fields" do
|
||||
login_as(users(:contributor_user))
|
||||
get new_request_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "h1", "Request a New Entry"
|
||||
assert_select "form"
|
||||
assert_select "input[name='entry[name]']", count: 0
|
||||
assert_select "input[name='entry[email]']", count: 0
|
||||
assert_select ".bg-blue-50", text: /Submitting as/
|
||||
end
|
||||
|
||||
test "should create entry request with valid data" do
|
||||
assert_difference(["User.count", "Entry.count"], 1) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "New Requester",
|
||||
email: "newrequester@example.com",
|
||||
category: "word",
|
||||
fi: "uusi sana",
|
||||
en: "new word",
|
||||
notes: "Please add this word"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
entry = Entry.last
|
||||
user = User.last
|
||||
|
||||
assert_equal "requested", entry.status
|
||||
assert_equal user, entry.requested_by
|
||||
assert_equal "New Requester", user.name
|
||||
assert_equal "newrequester@example.com", user.email
|
||||
assert_equal "contributor", user.role
|
||||
assert_nil user.invitation_token
|
||||
assert_redirected_to root_path
|
||||
assert_match(/thank you for your request/i, flash[:notice])
|
||||
end
|
||||
|
||||
test "should require at least one translation" do
|
||||
assert_no_difference(["User.count", "Entry.count"]) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "New Requester",
|
||||
email: "newrequester@example.com",
|
||||
category: "word",
|
||||
notes: "No translations provided"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_select ".bg-red-50", text: /At least one language translation is required/
|
||||
end
|
||||
|
||||
test "should redirect to login if email already exists" do
|
||||
existing_user = users(:contributor_user)
|
||||
|
||||
assert_no_difference(["User.count", "Entry.count"]) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Test User",
|
||||
email: existing_user.email,
|
||||
category: "word",
|
||||
fi: "sana",
|
||||
en: "word"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
assert_redirected_to login_path
|
||||
assert_equal "An account with this email already exists. Please log in.", flash[:alert]
|
||||
end
|
||||
|
||||
test "should show pending count for email with existing requests" do
|
||||
requester = users(:requester_user)
|
||||
get new_request_path, params: { email: requester.email }
|
||||
|
||||
assert_response :success
|
||||
# User has one requested entry from fixtures
|
||||
assert_select ".bg-blue-50", text: /1 pending request/
|
||||
end
|
||||
|
||||
test "should create entry with only one translation" do
|
||||
assert_difference(["User.count", "Entry.count"], 1) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Single Translation",
|
||||
email: "single@example.com",
|
||||
category: "word",
|
||||
fi: "vain suomeksi"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
entry = Entry.last
|
||||
assert_equal "vain suomeksi", entry.fi
|
||||
assert_nil entry.en
|
||||
assert_nil entry.sv
|
||||
assert_redirected_to root_path
|
||||
end
|
||||
|
||||
test "should create entry with multiple translations" do
|
||||
assert_difference(["User.count", "Entry.count"], 1) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Multi Lingual",
|
||||
email: "multilingual@example.com",
|
||||
category: "phrase",
|
||||
fi: "hyvää päivää",
|
||||
en: "good day",
|
||||
sv: "god dag",
|
||||
no: "god dag",
|
||||
ru: "добрый день",
|
||||
de: "guten Tag"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
entry = Entry.last
|
||||
assert_equal "hyvää päivää", entry.fi
|
||||
assert_equal "good day", entry.en
|
||||
assert_equal "god dag", entry.sv
|
||||
assert_equal "god dag", entry.no
|
||||
assert_equal "добрый день", entry.ru
|
||||
assert_equal "guten Tag", entry.de
|
||||
assert_redirected_to root_path
|
||||
end
|
||||
|
||||
test "logged-in user can submit request without providing name/email" do
|
||||
user = users(:contributor_user)
|
||||
login_as(user)
|
||||
|
||||
assert_no_difference("User.count") do
|
||||
assert_difference("Entry.count", 1) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
category: "word",
|
||||
fi: "kirjautunut käyttäjä",
|
||||
en: "logged in user"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
entry = Entry.last
|
||||
assert_equal user, entry.requested_by
|
||||
assert_equal "requested", entry.status
|
||||
assert_redirected_to root_path
|
||||
assert_match(/thank you for your request/i, flash[:notice])
|
||||
end
|
||||
|
||||
test "should not modify existing user when they submit request" do
|
||||
user = users(:contributor_user)
|
||||
original_name = user.name
|
||||
original_updated_at = user.updated_at
|
||||
login_as(user)
|
||||
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
category: "word",
|
||||
fi: "testi"
|
||||
}
|
||||
}
|
||||
|
||||
user.reload
|
||||
assert_equal original_name, user.name
|
||||
assert_equal original_updated_at.to_i, user.updated_at.to_i
|
||||
end
|
||||
|
||||
test "should reuse existing pending user without modifying them" do
|
||||
# Create a user without accepted invitation
|
||||
existing_user = User.create!(
|
||||
name: "Pending User",
|
||||
email: "pending_test@example.com",
|
||||
password: SecureRandom.alphanumeric(32),
|
||||
role: :contributor
|
||||
)
|
||||
original_name = existing_user.name
|
||||
original_updated_at = existing_user.updated_at
|
||||
|
||||
# Create first entry
|
||||
Entry.create!(
|
||||
category: :word,
|
||||
fi: "first",
|
||||
status: :requested,
|
||||
requested_by: existing_user
|
||||
)
|
||||
|
||||
# Submit second request with same email but different name
|
||||
assert_no_difference("User.count") do
|
||||
assert_difference("Entry.count", 1) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Different Name", # This should be ignored
|
||||
email: existing_user.email,
|
||||
category: "word",
|
||||
fi: "second"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
existing_user.reload
|
||||
assert_equal original_name, existing_user.name # Name should not change
|
||||
assert_equal original_updated_at.to_i, existing_user.updated_at.to_i # Should not be updated
|
||||
assert_equal 2, existing_user.requested_entries.count
|
||||
end
|
||||
end
|
||||
Vendored
+32
@@ -12,6 +12,7 @@ one:
|
||||
verified: false
|
||||
created_by: admin_user
|
||||
updated_by: admin_user
|
||||
status: 2 # active
|
||||
|
||||
two:
|
||||
category: 1
|
||||
@@ -25,3 +26,34 @@ two:
|
||||
verified: false
|
||||
created_by: contributor_user
|
||||
updated_by: contributor_user
|
||||
status: 2 # active
|
||||
|
||||
requested_entry:
|
||||
category: 0 # word
|
||||
fi: "testisana"
|
||||
en: "testword"
|
||||
sv: ~
|
||||
'no': ~
|
||||
ru: ~
|
||||
de: ~
|
||||
notes: "This is a test entry request"
|
||||
verified: false
|
||||
created_by: ~
|
||||
updated_by: ~
|
||||
status: 0 # requested
|
||||
requested_by: requester_user
|
||||
|
||||
approved_entry:
|
||||
category: 1 # phrase
|
||||
fi: "hyväksytty fraasi"
|
||||
en: "approved phrase"
|
||||
sv: ~
|
||||
'no': ~
|
||||
ru: ~
|
||||
de: ~
|
||||
notes: "This entry has been approved"
|
||||
verified: false
|
||||
created_by: ~
|
||||
updated_by: ~
|
||||
status: 1 # approved
|
||||
requested_by: pending_invitation
|
||||
|
||||
Vendored
+11
@@ -44,3 +44,14 @@ pending_invitation:
|
||||
invitation_sent_at: <%= 2.days.ago %>
|
||||
invitation_accepted_at: ~
|
||||
invited_by: admin_user
|
||||
|
||||
requester_user:
|
||||
email: "requester@example.com"
|
||||
password_digest: <%= BCrypt::Password.create('password123456') %>
|
||||
name: "Entry Requester"
|
||||
role: 0 # contributor
|
||||
primary_language: "en"
|
||||
invitation_token: ~
|
||||
invitation_sent_at: ~
|
||||
invitation_accepted_at: ~
|
||||
invited_by: ~
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
require "test_helper"
|
||||
|
||||
class EntryRequestFlowTest < ActionDispatch::IntegrationTest
|
||||
test "complete flow: request -> admin approve -> user accepts -> entry active" do
|
||||
# Step 1: Public user submits entry request
|
||||
assert_difference(["User.count", "Entry.count"], 1) do
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Flow Test User",
|
||||
email: "flowtest@example.com",
|
||||
category: "word",
|
||||
fi: "testi",
|
||||
en: "test",
|
||||
notes: "Testing complete flow"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
assert_redirected_to root_path
|
||||
follow_redirect!
|
||||
assert_response :success
|
||||
|
||||
entry = Entry.last
|
||||
user = User.last
|
||||
|
||||
assert_equal "requested", entry.status
|
||||
assert_equal user, entry.requested_by
|
||||
assert_nil user.invitation_token
|
||||
|
||||
# Step 2: Admin reviews and approves request
|
||||
admin = users(:admin_user)
|
||||
login_as(admin)
|
||||
|
||||
get admin_requests_path
|
||||
assert_response :success
|
||||
assert_select "td", text: entry.fi
|
||||
|
||||
# View the request
|
||||
get admin_request_path(entry)
|
||||
assert_response :success
|
||||
assert_select "div", text: entry.fi
|
||||
|
||||
# Approve the request
|
||||
assert_enqueued_emails 1 do
|
||||
post approve_admin_request_path(entry)
|
||||
end
|
||||
|
||||
entry.reload
|
||||
user.reload
|
||||
|
||||
assert_equal "approved", entry.status
|
||||
assert_not_nil user.invitation_token
|
||||
assert_not_nil user.invitation_sent_at
|
||||
assert_equal admin, user.invited_by
|
||||
|
||||
# Verify the invitation email includes entry information
|
||||
perform_enqueued_jobs
|
||||
sent_email = ActionMailer::Base.deliveries.last
|
||||
assert_match "Your entry request has been approved", sent_email.subject
|
||||
assert_match entry.fi, sent_email.body.encoded
|
||||
assert_match entry.en, sent_email.body.encoded
|
||||
|
||||
logout
|
||||
|
||||
# Step 3: User accepts invitation
|
||||
get invitation_path(user.invitation_token)
|
||||
assert_response :success
|
||||
|
||||
patch accept_invitation_path(user.invitation_token), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
entry.reload
|
||||
user.reload
|
||||
|
||||
# Entry should now be active
|
||||
assert_equal "active", entry.status
|
||||
assert_not_nil user.invitation_accepted_at
|
||||
assert_nil user.invitation_token
|
||||
assert_equal user.id, session[:user_id]
|
||||
|
||||
# Step 4: Verify entry is visible on public site
|
||||
get root_path
|
||||
assert_response :success
|
||||
# Entry should be in search results
|
||||
end
|
||||
|
||||
test "rejected request removes entry and user" do
|
||||
# Create a request
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "To Be Rejected",
|
||||
email: "rejected@example.com",
|
||||
category: "word",
|
||||
fi: "hylätty",
|
||||
en: "rejected"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
|
||||
entry = Entry.last
|
||||
user = User.last
|
||||
entry_id = entry.id
|
||||
user_id = user.id
|
||||
|
||||
# Admin rejects it
|
||||
login_as(users(:admin_user))
|
||||
|
||||
assert_difference(["User.count", "Entry.count"], -1) do
|
||||
delete reject_admin_request_path(entry)
|
||||
end
|
||||
|
||||
assert_not Entry.exists?(entry_id)
|
||||
assert_not User.exists?(user_id)
|
||||
end
|
||||
|
||||
test "requested and approved entries not visible on public site" do
|
||||
requested = entries(:requested_entry)
|
||||
approved = entries(:approved_entry)
|
||||
active = entries(:one)
|
||||
|
||||
get root_path
|
||||
assert_response :success
|
||||
|
||||
# Active entry should be counted
|
||||
assert_match /#{Entry.active_entries.count}/, response.body
|
||||
|
||||
# Verify counts exclude requested/approved entries
|
||||
total_entries = Entry.count
|
||||
active_entries = Entry.active_entries.count
|
||||
|
||||
assert total_entries > active_entries, "Should have non-active entries in fixtures"
|
||||
end
|
||||
|
||||
test "multiple entries by same requester all activated on invitation acceptance" do
|
||||
# Create user with multiple approved entries
|
||||
user = User.create!(
|
||||
name: "Multi Entry User",
|
||||
email: "multi@example.com",
|
||||
password: SecureRandom.alphanumeric(32),
|
||||
role: :contributor,
|
||||
invitation_token: "multi_token_123",
|
||||
invitation_sent_at: 1.day.ago,
|
||||
invited_by: users(:admin_user)
|
||||
)
|
||||
|
||||
entry1 = Entry.create!(
|
||||
category: :word,
|
||||
fi: "sana1",
|
||||
en: "word1",
|
||||
status: :approved,
|
||||
requested_by: user
|
||||
)
|
||||
|
||||
entry2 = Entry.create!(
|
||||
category: :phrase,
|
||||
fi: "fraasi1",
|
||||
en: "phrase1",
|
||||
status: :approved,
|
||||
requested_by: user
|
||||
)
|
||||
|
||||
# User accepts invitation
|
||||
patch accept_invitation_path("multi_token_123"), params: {
|
||||
user: {
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
}
|
||||
}
|
||||
|
||||
entry1.reload
|
||||
entry2.reload
|
||||
|
||||
assert_equal "active", entry1.status
|
||||
assert_equal "active", entry2.status
|
||||
end
|
||||
|
||||
test "admin can edit entry details before approval" do
|
||||
# Create a request
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Needs Editing",
|
||||
email: "edit@example.com",
|
||||
category: "word",
|
||||
fi: "väärin kirjoitettu",
|
||||
en: "wrong spelling"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to root_path
|
||||
|
||||
entry = Entry.last
|
||||
|
||||
# Admin edits the entry
|
||||
login_as(users(:admin_user))
|
||||
|
||||
get edit_admin_request_path(entry)
|
||||
assert_response :success
|
||||
|
||||
patch admin_request_path(entry), params: {
|
||||
entry: {
|
||||
fi: "oikein kirjoitettu",
|
||||
en: "correct spelling",
|
||||
category: "phrase"
|
||||
}
|
||||
}
|
||||
|
||||
entry.reload
|
||||
assert_equal "oikein kirjoitettu", entry.fi
|
||||
assert_equal "correct spelling", entry.en
|
||||
assert_equal "phrase", entry.category
|
||||
|
||||
# Then approve with corrected details
|
||||
post approve_admin_request_path(entry)
|
||||
|
||||
entry.reload
|
||||
assert_equal "approved", entry.status
|
||||
assert_equal "oikein kirjoitettu", entry.fi
|
||||
end
|
||||
|
||||
test "cannot submit request with existing user email" do
|
||||
existing_user = users(:contributor_user)
|
||||
|
||||
post requests_path, params: {
|
||||
entry: {
|
||||
name: "Test",
|
||||
email: existing_user.email,
|
||||
category: "word",
|
||||
fi: "test"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to login_path
|
||||
assert_match(/already exists/i, flash[:alert])
|
||||
end
|
||||
end
|
||||
@@ -35,4 +35,50 @@ class InvitationMailerTest < ActionMailer::TestCase
|
||||
assert_equal "text/plain", mail.text_part.content_type.split(";").first
|
||||
assert_equal "text/html", mail.html_part.content_type.split(";").first
|
||||
end
|
||||
|
||||
test "invite with approved_entry includes entry details" do
|
||||
user = users(:requester_user)
|
||||
user.update!(
|
||||
invitation_token: SecureRandom.urlsafe_base64(32),
|
||||
invitation_sent_at: Time.current
|
||||
)
|
||||
entry = entries(:requested_entry)
|
||||
mail = InvitationMailer.invite(user, approved_entry: entry)
|
||||
|
||||
assert_equal "Your entry request has been approved - Join Sanasto Wiki", mail.subject
|
||||
assert_equal [ user.email ], mail.to
|
||||
|
||||
# Check that entry details are included
|
||||
assert_match entry.fi, mail.body.encoded
|
||||
assert_match entry.en, mail.body.encoded
|
||||
assert_match entry.category.to_s.humanize, mail.body.encoded
|
||||
assert_match "approved", mail.body.encoded.downcase
|
||||
end
|
||||
|
||||
test "invite with approved_entry shows correct message" do
|
||||
user = users(:requester_user)
|
||||
user.update!(
|
||||
invitation_token: SecureRandom.urlsafe_base64(32),
|
||||
invitation_sent_at: Time.current
|
||||
)
|
||||
entry = entries(:requested_entry)
|
||||
mail = InvitationMailer.invite(user, approved_entry: entry)
|
||||
|
||||
# HTML part should contain the entry box
|
||||
assert_match "Your Approved Entry", mail.html_part.body.encoded
|
||||
assert_match "entry-box", mail.html_part.body.encoded
|
||||
|
||||
# Text part should contain entry details
|
||||
assert_match "YOUR APPROVED ENTRY", mail.text_part.body.encoded
|
||||
assert_match "Category:", mail.text_part.body.encoded
|
||||
end
|
||||
|
||||
test "invite without approved_entry uses standard message" do
|
||||
user = users(:pending_invitation)
|
||||
mail = InvitationMailer.invite(user)
|
||||
|
||||
assert_equal "You've been invited to join Sanasto Wiki", mail.subject
|
||||
assert_match "you can contribute to this work", mail.body.encoded.downcase
|
||||
assert_no_match "approved", mail.body.encoded.downcase
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
require "test_helper"
|
||||
|
||||
class EntryRequestTest < ActiveSupport::TestCase
|
||||
test "entry requires at least one translation" do
|
||||
entry = Entry.new(
|
||||
category: :word,
|
||||
status: :requested
|
||||
)
|
||||
|
||||
assert_not entry.valid?
|
||||
assert_includes entry.errors[:base], "At least one language translation is required"
|
||||
end
|
||||
|
||||
test "entry is valid with one translation" do
|
||||
entry = Entry.new(
|
||||
category: :word,
|
||||
fi: "sana",
|
||||
status: :requested
|
||||
)
|
||||
|
||||
assert entry.valid?
|
||||
end
|
||||
|
||||
test "entry is valid with multiple translations" do
|
||||
entry = Entry.new(
|
||||
category: :word,
|
||||
fi: "sana",
|
||||
en: "word",
|
||||
sv: "ord",
|
||||
status: :requested
|
||||
)
|
||||
|
||||
assert entry.valid?
|
||||
end
|
||||
|
||||
test "entry status defaults to active" do
|
||||
entry = Entry.new(
|
||||
category: :word,
|
||||
fi: "test"
|
||||
)
|
||||
|
||||
assert_equal "active", entry.status
|
||||
end
|
||||
|
||||
test "requested scope returns only requested entries" do
|
||||
requested_entries = Entry.requested
|
||||
assert_includes requested_entries, entries(:requested_entry)
|
||||
assert_not_includes requested_entries, entries(:one)
|
||||
assert_not_includes requested_entries, entries(:approved_entry)
|
||||
end
|
||||
|
||||
test "approved scope returns only approved entries" do
|
||||
approved_entries = Entry.approved
|
||||
assert_includes approved_entries, entries(:approved_entry)
|
||||
assert_not_includes approved_entries, entries(:one)
|
||||
assert_not_includes approved_entries, entries(:requested_entry)
|
||||
end
|
||||
|
||||
test "active_entries scope returns only active entries" do
|
||||
active_entries = Entry.active_entries
|
||||
assert_includes active_entries, entries(:one)
|
||||
assert_includes active_entries, entries(:two)
|
||||
assert_not_includes active_entries, entries(:requested_entry)
|
||||
assert_not_includes active_entries, entries(:approved_entry)
|
||||
end
|
||||
|
||||
test "requested_by association" do
|
||||
entry = entries(:requested_entry)
|
||||
requester = users(:requester_user)
|
||||
|
||||
assert_equal requester, entry.requested_by
|
||||
end
|
||||
|
||||
test "user has requested_entries association" do
|
||||
requester = users(:requester_user)
|
||||
|
||||
assert_includes requester.requested_entries, entries(:requested_entry)
|
||||
end
|
||||
|
||||
test "can create entry with requested status" do
|
||||
user = users(:requester_user)
|
||||
entry = Entry.create!(
|
||||
category: :word,
|
||||
fi: "uusi",
|
||||
en: "new",
|
||||
status: :requested,
|
||||
requested_by: user
|
||||
)
|
||||
|
||||
assert_equal "requested", entry.status
|
||||
assert_equal user, entry.requested_by
|
||||
end
|
||||
|
||||
test "can transition entry from requested to approved to active" do
|
||||
entry = entries(:requested_entry)
|
||||
|
||||
assert_equal "requested", entry.status
|
||||
|
||||
entry.update!(status: :approved)
|
||||
assert_equal "approved", entry.status
|
||||
|
||||
entry.update!(status: :active)
|
||||
assert_equal "active", entry.status
|
||||
end
|
||||
|
||||
test "entries count does not include requested or approved" do
|
||||
total = Entry.count
|
||||
active = Entry.active_entries.count
|
||||
requested = Entry.requested.count
|
||||
approved = Entry.approved.count
|
||||
|
||||
assert_equal total, active + requested + approved
|
||||
assert requested > 0, "Should have at least one requested entry in fixtures"
|
||||
assert approved > 0, "Should have at least one approved entry in fixtures"
|
||||
end
|
||||
|
||||
test "blank translations are not counted as having translation" do
|
||||
entry = Entry.new(
|
||||
category: :word,
|
||||
fi: "",
|
||||
en: " ",
|
||||
sv: nil
|
||||
)
|
||||
|
||||
assert_not entry.valid?
|
||||
assert_includes entry.errors[:base], "At least one language translation is required"
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,8 @@
|
||||
require "test_helper"
|
||||
|
||||
class EntryTest < ActiveSupport::TestCase
|
||||
test "should be valid with a category" do
|
||||
entry = Entry.new(category: :word)
|
||||
test "should be valid with a category and at least one translation" do
|
||||
entry = Entry.new(category: :word, fi: "test")
|
||||
assert entry.valid?
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user