* Only permit valid class names as sponsorable type https://github.com/forem/forem/issues/14386 identified an error could arise if input was provide to the sponsorable_type field in the admin creation form, but it was not a valid constant (since we include the related model in the index when loading @sponsorships, this permits creating a sponsorship that can't easily be managed or deleted). Add a validation to ensure when the sponsorable type is present, it's a class (really, we probably want to also ensure it's available as an associated model type, since we'll be looking it up with find(:sponsorable_id), but this is an initial attempt at adding some guard rails to this form). A more realistic solution would be to add a `Sponsorship::SPONSORABLE_TYPES` constant to the class, and validate the supplied sponsorable type is in `SPONSORABLE_TYPES.map(&:name)`. That requires more domain knowledge about what kind of things can be sponsored than I have, it would certainly include at least ActsAsTaggableOn::Tag but may include other classes (or why would it be polymorphic). * Explicitly list the allowed types for sponsorship I see we can sponsor tags, and suspect that's all we can sponsor meaningfully (the view only shows details when it is a tag). * Use inclusion validation with options to replicate custom method And remove the code, we don't need it any more. * Update app/models/sponsorship.rb Co-authored-by: Michael Kohl <citizen428@dev.to> Co-authored-by: Michael Kohl <citizen428@dev.to>
122 lines
5.2 KiB
Ruby
122 lines
5.2 KiB
Ruby
require "rails_helper"
|
|
|
|
RSpec.describe Sponsorship, type: :model do
|
|
describe "constants" do
|
|
it "has the correct values for constants" do
|
|
expect(Sponsorship::LEVELS).to eq(%w[gold silver bronze tag media devrel])
|
|
expect(Sponsorship::METAL_LEVELS).to eq(%w[gold silver bronze])
|
|
expect(Sponsorship::STATUSES).to eq(%w[none pending live])
|
|
expected_credits = { gold: 6000, silver: 500, bronze: 100, tag: 300, devrel: 500 }.with_indifferent_access
|
|
expect(Sponsorship::CREDITS).to eq(expected_credits)
|
|
end
|
|
end
|
|
|
|
describe "#url" do
|
|
let(:sponsorship) { build(:sponsorship) }
|
|
|
|
it "accepts a blank url" do
|
|
sponsorship.url = ""
|
|
expect(sponsorship).to be_valid
|
|
end
|
|
|
|
it "accepts a HTTP url" do
|
|
sponsorship.url = "http://example.com"
|
|
expect(sponsorship).to be_valid
|
|
end
|
|
|
|
it "accepts a HTTPS url" do
|
|
sponsorship.url = "https://example.com"
|
|
expect(sponsorship).to be_valid
|
|
end
|
|
|
|
it "does not accept an invalid url" do
|
|
sponsorship.url = "example.com"
|
|
expect(sponsorship).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe "validations" do
|
|
let(:user) { create(:user, :org_member) }
|
|
let(:org) { user.organizations.first }
|
|
|
|
describe "builtin validations" do
|
|
it { is_expected.to belong_to(:organization).inverse_of(:sponsorships) }
|
|
it { is_expected.to belong_to(:sponsorable).optional }
|
|
it { is_expected.to belong_to(:user) }
|
|
|
|
it { is_expected.to have_db_index(%i[sponsorable_id sponsorable_type]) }
|
|
it { is_expected.to have_db_index(:level) }
|
|
it { is_expected.to have_db_index(:status) }
|
|
|
|
it { is_expected.to validate_inclusion_of(:level).in_array(Sponsorship::LEVELS) }
|
|
it { is_expected.to validate_inclusion_of(:status).in_array(Sponsorship::STATUSES) }
|
|
|
|
it { is_expected.to validate_presence_of(:level) }
|
|
it { is_expected.to validate_presence_of(:organization) }
|
|
it { is_expected.to validate_presence_of(:status) }
|
|
it { is_expected.to validate_presence_of(:user) }
|
|
|
|
it { is_expected.not_to allow_values(nil).for(:featured_number) }
|
|
it { is_expected.to allow_values(nil).for(:expires_at) }
|
|
end
|
|
|
|
it "forbids an org to have multiple 'expiring' (bronze-silver-gold) sponsorships" do
|
|
create(:sponsorship, level: :gold, organization: org, expires_at: 2.days.from_now)
|
|
bronze_sponsorship = build(:sponsorship, level: :bronze, organization: org)
|
|
expect(bronze_sponsorship).not_to be_valid
|
|
expect(bronze_sponsorship.errors[:level]).to be_present
|
|
end
|
|
|
|
it "allows to create a new sponsorship for the same org if the previous one is expired" do
|
|
create(:sponsorship, expires_at: 1.day.ago, user: user, organization: org, level: :bronze)
|
|
bronze_sponsorship = build(:sponsorship, level: :bronze, user: user, organization: org)
|
|
expect(bronze_sponsorship).to be_valid
|
|
end
|
|
|
|
it "allows to create a new sponsorship for the same level for another org" do
|
|
create(:sponsorship, level: :gold, organization: org, expires_at: 2.days.from_now)
|
|
other_org = create(:organization)
|
|
gold_sponsorship = build(:sponsorship, level: :gold, organization: other_org)
|
|
expect(gold_sponsorship).to be_valid
|
|
end
|
|
|
|
context "when tag sponsorships" do
|
|
let(:python) { create(:tag, name: "python") }
|
|
let(:ruby) { create(:tag, name: "ruby") }
|
|
|
|
it "allows an org to have multiple tag sponsorships on different tags" do
|
|
create(:sponsorship, level: :tag, organization: org, expires_at: 2.days.from_now, sponsorable: python)
|
|
tag_sponsorship = build(:sponsorship, level: :tag, organization: org, sponsorable: ruby)
|
|
expect(tag_sponsorship).to be_valid
|
|
end
|
|
|
|
it "allows to create a new tag sponsorship if the previous one is expired" do
|
|
create(:sponsorship, user: user, organization: org, expires_at: 1.day.ago, level: :tag, sponsorable: ruby)
|
|
ruby_sponsorship = build(:sponsorship, user: user, organization: org, level: :tag, sponsorable: ruby)
|
|
expect(ruby_sponsorship).to be_valid
|
|
end
|
|
|
|
it "forbids an org to have multiple active tag sponsorships on the same tag" do
|
|
tag = create(:tag, name: "python")
|
|
create(:sponsorship, level: :tag, organization: org, sponsorable: tag, expires_at: 2.days.from_now)
|
|
tag_sponsorship = build(:sponsorship, level: :tag, organization: org, sponsorable: tag)
|
|
expect(tag_sponsorship).not_to be_valid
|
|
expect(tag_sponsorship.errors[:level]).to be_present
|
|
end
|
|
|
|
it "forbids different orgs to have active sponsorships on the same tag" do
|
|
other_org = create(:organization)
|
|
create(:sponsorship, level: :tag, organization: org, sponsorable: python, expires_at: 2.days.from_now)
|
|
tag_sponsorship = build(:sponsorship, level: :tag, organization: other_org, sponsorable: python)
|
|
expect(tag_sponsorship).not_to be_valid
|
|
expect(tag_sponsorship.errors[:level]).to be_present
|
|
end
|
|
|
|
it "ensures sponsorable_type is loadable when present" do
|
|
sponsorship = build(:sponsorship, user: user, organization: org, sponsorable_type: "Unsponsorable")
|
|
expect(sponsorship).not_to be_valid
|
|
expect(sponsorship.errors[:sponsorable_type]).to include("is not a sponsorable type")
|
|
end
|
|
end
|
|
end
|
|
end
|