Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 2 additions & 173 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,179 +82,8 @@ def find_or_create_by_name!(klass, name, **attrs, &block)
user.confirmed_at = Time.current
end

# Invited but hasn't clicked the link yet
invited = User.find_or_create_by!(email: "invited.pending@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless invited.confirmed_at.present?
invited.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 3.days.ago,
welcome_instructions_sent_at: 3.days.ago
)
end
unless invited.person.present?
person = Person.create!(
first_name: "Invited",
last_name: "Pending",
email: invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
invited.update!(person: person)
end

# Clicked confirmation link but didn't set password
confirmed_no_pw = User.find_or_create_by!(email: "confirmed.nopassword@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless confirmed_no_pw.welcome_instructions_token.present?
token = Devise.friendly_token
confirmed_no_pw.update_columns(
confirmed_at: 2.days.ago,
welcome_instructions_token: token,
welcome_instructions_created_at: 5.days.ago,
welcome_instructions_sent_at: 5.days.ago
)
end
unless confirmed_no_pw.person.present?
person = Person.create!(
first_name: "Confirmed",
last_name: "NoPassword",
email: confirmed_no_pw.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
confirmed_no_pw.update!(person: person)
end

# Locked account (too many failed attempts)
locked = User.find_or_create_by!(email: "locked.user@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = 1.month.ago
end
unless locked.locked_at.present?
locked.update_columns(
locked_at: 1.day.ago,
failed_attempts: Devise.maximum_attempts
)
end
unless locked.person.present?
person = Person.create!(
first_name: "Locked",
last_name: "User",
email: locked.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
locked.update!(person: person)
end

# Never invited (created but no confirmation sent)
never_invited = User.find_or_create_by!(email: "never.invited@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless never_invited.welcome_instructions_sent_at.present?
never_invited.update_columns(confirmed_at: nil)
end
unless never_invited.person.present?
person = Person.create!(
first_name: "Never",
last_name: "Invited",
email: never_invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
never_invited.update!(person: person)
end

# Invited a while ago, never clicked (stale invite)
stale_invited = User.find_or_create_by!(email: "stale.invite@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless stale_invited.confirmed_at.present?
stale_invited.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 45.days.ago,
welcome_instructions_sent_at: 45.days.ago
)
end
unless stale_invited.person.present?
person = Person.create!(
first_name: "Stale",
last_name: "Invite",
email: stale_invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
stale_invited.update!(person: person)
end

# Invited yesterday
recent_invited = User.find_or_create_by!(email: "recent.invite@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless recent_invited.confirmed_at.present?
recent_invited.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 1.day.ago,
welcome_instructions_sent_at: 1.day.ago
)
end
unless recent_invited.person.present?
person = Person.create!(
first_name: "Recent",
last_name: "Invite",
email: recent_invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
recent_invited.update!(person: person)
end

# Never invited, no person record either
User.find_or_create_by!(email: "orphan.uninvited@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end.tap do |u|
u.update_columns(confirmed_at: nil) unless u.welcome_instructions_sent_at.present?
end

# Invited, no person record
invited_no_person = User.find_or_create_by!(email: "invited.noperson@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless invited_no_person.confirmed_at.present?
invited_no_person.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 10.days.ago,
welcome_instructions_sent_at: 10.days.ago
)
end
# Dev-only user variations (invite/lock/confirmation edge cases) live in
# db/seeds/dev/users.rb and run via `rake db:seed:users` / `db:seed:dev`.

# Only reset seed-user passwords, not every user in the database
seed_emails = %w[umberto.user@example.com amy.user@example.com aisha.user@example.com orphaned_reports@awbw.org]
Expand Down
198 changes: 198 additions & 0 deletions db/seeds/dev/users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Dev-only user variations - run on their own via `rake db:seed:users`, or as
# part of `rake db:seed:dev`. These exercise login/invite/lock edge cases and
# must never run in production, so they live here rather than in `db/seeds.rb`
# (which seeds only the required base users: Umberto, Amy, Aisha, Orphaned).

puts "Seeding dev user variations (invite/lock/confirmation states)…"

# created_by/updated_by point at the base admin, seeded by `db:seed` first.
admin = User.find_by!(email: "umberto.user@example.com")

# Invited but hasn't clicked the link yet
invited = User.find_or_create_by!(email: "invited.pending@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless invited.welcome_instructions_token.present?
invited.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 3.days.ago,
welcome_instructions_sent_at: 3.days.ago
)
end
unless invited.person.present?
person = Person.create!(
first_name: "Invited",
last_name: "Pending",
email: invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
invited.update!(person: person)
end

# Clicked confirmation link but didn't set password
confirmed_no_pw = User.find_or_create_by!(email: "confirmed.nopassword@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless confirmed_no_pw.welcome_instructions_token.present?
token = Devise.friendly_token
confirmed_no_pw.update_columns(
confirmed_at: 2.days.ago,
welcome_instructions_token: token,
welcome_instructions_created_at: 5.days.ago,
welcome_instructions_sent_at: 5.days.ago
)
end
unless confirmed_no_pw.person.present?
person = Person.create!(
first_name: "Confirmed",
last_name: "NoPassword",
email: confirmed_no_pw.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
confirmed_no_pw.update!(person: person)
end

# Locked account (too many failed attempts)
locked = User.find_or_create_by!(email: "locked.user@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = 1.month.ago
end
unless locked.locked_at.present?
locked.update_columns(
locked_at: 1.day.ago,
failed_attempts: Devise.maximum_attempts
)
end
unless locked.person.present?
person = Person.create!(
first_name: "Locked",
last_name: "User",
email: locked.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
locked.update!(person: person)
end

# Never invited (created but no confirmation sent)
never_invited = User.find_or_create_by!(email: "never.invited@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless never_invited.welcome_instructions_sent_at.present?
never_invited.update_columns(confirmed_at: nil)
end
unless never_invited.person.present?
person = Person.create!(
first_name: "Never",
last_name: "Invited",
email: never_invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
never_invited.update!(person: person)
end

# Invited a while ago, never clicked (stale invite)
stale_invited = User.find_or_create_by!(email: "stale.invite@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless stale_invited.welcome_instructions_token.present?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Guard keys on welcome_instructions_token, not confirmed_atfind_or_create_by! just set confirmed_at to Time.current, so the old unless confirmed_at.present? never fired and these "stale/pending invite" users ended up confirmed. Token presence is the real "invite already set up" marker, so this runs once and stays idempotent.

stale_invited.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 45.days.ago,
welcome_instructions_sent_at: 45.days.ago
)
end
unless stale_invited.person.present?
person = Person.create!(
first_name: "Stale",
last_name: "Invite",
email: stale_invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
stale_invited.update!(person: person)
end

# Invited yesterday
recent_invited = User.find_or_create_by!(email: "recent.invite@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless recent_invited.welcome_instructions_token.present?
recent_invited.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 1.day.ago,
welcome_instructions_sent_at: 1.day.ago
)
end
unless recent_invited.person.present?
person = Person.create!(
first_name: "Recent",
last_name: "Invite",
email: recent_invited.email,
created_by: admin,
updated_by: admin,
profile_is_searchable: true
)
recent_invited.update!(person: person)
end

# Never invited, no person record either
User.find_or_create_by!(email: "orphan.uninvited@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end.tap do |u|
u.update_columns(confirmed_at: nil) unless u.welcome_instructions_sent_at.present?
end

# Invited, no person record
invited_no_person = User.find_or_create_by!(email: "invited.noperson@example.com") do |user|
user.password = "password"
user.super_user = false
user.confirmed_at = Time.current
end
unless invited_no_person.welcome_instructions_token.present?
invited_no_person.update_columns(
confirmed_at: nil,
welcome_instructions_token: Devise.friendly_token,
welcome_instructions_created_at: 10.days.ago,
welcome_instructions_sent_at: 10.days.ago
)
end

# Reset only these dev users' passwords (mirrors the base seed reset), so they
# stay loggable-in after a reseed without touching any other user.
dev_user_emails = %w[
invited.pending@example.com
confirmed.nopassword@example.com
locked.user@example.com
never.invited@example.com
stale.invite@example.com
recent.invite@example.com
orphan.uninvited@example.com
invited.noperson@example.com
]
dev_user_password = Devise::Encryptor.digest(User, "password")
User.where(email: dev_user_emails).update_all(encrypted_password: dev_user_password)
7 changes: 6 additions & 1 deletion lib/tasks/dev.rake
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace :db do
namespace :seed do
desc "Generate representative sample data for development"
task dev: [ :environment, "db:seed", "db:seed:dummy", "db:seed:payments" ]
task dev: [ :environment, "db:seed", "db:seed:users", "db:seed:dummy", "db:seed:payments" ]

@maebeale maebeale Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Correction — placement of db:seed:users here is just grouping, not a hard dependency: dummy.rb/payments.rb reference only the base users (umberto/amy/aisha) created by db:seed, not these dev variations. Ordering relative to dummy/payments is cosmetic.


desc "Seed dev-only user variations (invite/lock/confirmation states)"
task users: :environment do
load Rails.root.join("db/seeds/dev/users.rb")
end
Comment on lines +6 to +9

desc "Seed generic dummy dev data (workshops, people, stories, etc.)"
task dummy: :environment do
Expand Down