[deploy] Improve some validations (#7335)
* Validations * Fix specs * Revert twitter tag change
This commit is contained in:
parent
8f6fb35404
commit
0a6be8b967
9 changed files with 228 additions and 136 deletions
|
|
@ -1,59 +1,61 @@
|
|||
class Organization < ApplicationRecord
|
||||
include CloudinaryHelper
|
||||
|
||||
COLOR_HEX_REGEXP = /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/.freeze
|
||||
INTEGER_REGEXP = /\A\d+\z/.freeze
|
||||
SLUG_REGEXP = /\A[a-zA-Z0-9\-_]+\z/.freeze
|
||||
MESSAGES = {
|
||||
integer_only: "Integer only. No sign allowed.",
|
||||
reserved_word: "%<value>s is a reserved word. Contact site admins for help registering your organization."
|
||||
}.freeze
|
||||
|
||||
acts_as_followable
|
||||
|
||||
has_many :job_listings
|
||||
has_many :organization_memberships, dependent: :delete_all
|
||||
has_many :users, through: :organization_memberships
|
||||
has_many :api_secrets, through: :users
|
||||
has_many :articles
|
||||
has_many :classified_listings
|
||||
has_many :collections
|
||||
has_many :credits
|
||||
has_many :display_ads
|
||||
has_many :notifications
|
||||
has_many :credits
|
||||
has_many :unspent_credits, -> { where spent: false }, class_name: "Credit", inverse_of: :organization
|
||||
has_many :classified_listings
|
||||
has_many :organization_memberships, dependent: :delete_all
|
||||
has_many :profile_pins, as: :profile, inverse_of: :profile
|
||||
has_many :sponsorships
|
||||
has_many :unspent_credits, -> { where spent: false }, class_name: "Credit", inverse_of: :organization
|
||||
has_many :users, through: :organization_memberships
|
||||
|
||||
validates :name, :summary, :url, :profile_image, presence: true
|
||||
validates :name,
|
||||
length: { maximum: 50 }
|
||||
validates :summary,
|
||||
length: { maximum: 250 }
|
||||
validates :tag_line,
|
||||
length: { maximum: 60 }
|
||||
validates :text_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true
|
||||
validates :bg_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true
|
||||
validates :slug,
|
||||
presence: true,
|
||||
uniqueness: { case_sensitive: false },
|
||||
format: { with: /\A[a-zA-Z0-9\-_]+\Z/ },
|
||||
length: { in: 2..18 },
|
||||
exclusion: { in: ReservedWords.all,
|
||||
message: "%<value>s is a reserved word. Contact site admins for help registering your organization." }
|
||||
validates :url, url: { allow_blank: true, no_local: true, schemes: %w[https http] }
|
||||
validates :secret, uniqueness: { allow_blank: true }
|
||||
validates :location, :email, :company_size, length: { maximum: 64 }
|
||||
validates :company_size, format: { with: /\A\d+\z/,
|
||||
message: "Integer only. No sign allowed.",
|
||||
allow_blank: true }
|
||||
validates :tech_stack, :story, length: { maximum: 640 }
|
||||
validates :cta_button_url,
|
||||
url: { allow_blank: true, no_local: true, schemes: %w[https http] }
|
||||
validates :cta_button_text, length: { maximum: 20 }
|
||||
validates :bg_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true
|
||||
validates :company_size, format: { with: INTEGER_REGEXP, message: MESSAGES[:integer_only], allow_blank: true }
|
||||
validates :company_size, length: { maximum: 7 }, allow_nil: true
|
||||
validates :cta_body_markdown, length: { maximum: 256 }
|
||||
before_save :remove_at_from_usernames
|
||||
after_save :bust_cache
|
||||
before_save :generate_secret
|
||||
before_save :update_articles
|
||||
before_validation :downcase_slug
|
||||
before_validation :check_for_slug_change
|
||||
before_validation :evaluate_markdown
|
||||
validates :cta_button_text, length: { maximum: 20 }
|
||||
validates :cta_button_url, length: { maximum: 150 }, url: { allow_blank: true, no_local: true }
|
||||
validates :github_username, length: { maximum: 50 }
|
||||
validates :location, :email, length: { maximum: 64 }
|
||||
validates :name, :summary, :url, :profile_image, presence: true
|
||||
validates :name, length: { maximum: 50 }
|
||||
validates :proof, length: { maximum: 1500 }
|
||||
validates :secret, length: { is: 100 }, uniqueness: { allow_blank: true }, allow_blank: true
|
||||
validates :slug, exclusion: { in: ReservedWords.all, message: MESSAGES[:reserved_word] }
|
||||
validates :slug, format: { with: SLUG_REGEXP }, length: { in: 2..18 }
|
||||
validates :slug, presence: true, uniqueness: { case_sensitive: false }
|
||||
validates :summary, length: { maximum: 250 }
|
||||
validates :tag_line, length: { maximum: 60 }
|
||||
validates :tech_stack, :story, length: { maximum: 640 }
|
||||
validates :text_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true
|
||||
validates :twitter_username, length: { maximum: 15 }
|
||||
validates :url, length: { maximum: 200 }, url: { allow_blank: true, no_local: true }
|
||||
|
||||
validate :unique_slug_including_users_and_podcasts, if: :slug_changed?
|
||||
|
||||
after_save :bust_cache
|
||||
before_save :generate_secret
|
||||
before_save :remove_at_from_usernames
|
||||
before_save :update_articles
|
||||
before_validation :check_for_slug_change
|
||||
before_validation :downcase_slug
|
||||
before_validation :evaluate_markdown
|
||||
|
||||
after_commit :sync_related_elasticsearch_docs, on: %i[update destroy]
|
||||
|
||||
mount_uploader :profile_image, ProfileImageUploader
|
||||
|
|
@ -117,7 +119,7 @@ class Organization < ApplicationRecord
|
|||
end
|
||||
|
||||
def downcase_slug
|
||||
self.slug = slug.downcase
|
||||
self.slug = slug&.downcase
|
||||
end
|
||||
|
||||
def update_articles
|
||||
|
|
@ -138,7 +140,14 @@ class Organization < ApplicationRecord
|
|||
end
|
||||
|
||||
def unique_slug_including_users_and_podcasts
|
||||
errors.add(:slug, "is taken.") if User.find_by(username: slug) || Podcast.find_by(slug: slug) || Page.find_by(slug: slug) || slug.include?("sitemap-")
|
||||
slug_taken = (
|
||||
User.exists?(username: slug) ||
|
||||
Podcast.exists?(slug: slug) ||
|
||||
Page.exists?(slug: slug) ||
|
||||
slug&.include?("sitemap-")
|
||||
)
|
||||
|
||||
errors.add(:slug, "is taken.") if slug_taken
|
||||
end
|
||||
|
||||
def sync_related_elasticsearch_docs
|
||||
|
|
|
|||
|
|
@ -1,5 +1,33 @@
|
|||
class User < ApplicationRecord
|
||||
include AlgoliaSearch
|
||||
include CloudinaryHelper
|
||||
include Searchable
|
||||
include Storext.model
|
||||
|
||||
BEHANCE_URL_REGEXP = /\A(http(s)?:\/\/)?(www.behance.net|behance.net)\/.*\z/.freeze
|
||||
COLOR_HEX_REGEXP = /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/.freeze
|
||||
DRIBBBLE_URL_REGEXP = /\A(http(s)?:\/\/)?(www.dribbble.com|dribbble.com)\/.*\z/.freeze
|
||||
EDITORS = %w[v1 v2].freeze
|
||||
FACEBOOK_URL_REGEXP = /\A(http(s)?:\/\/)?(www.facebook.com|facebook.com)\/.*\z/.freeze
|
||||
FONTS = %w[default sans_serif monospace comic_sans open_dyslexic].freeze
|
||||
GITLAB_URL_REGEXP = /\A(http(s)?:\/\/)?(www.gitlab.com|gitlab.com)\/.*\z/.freeze
|
||||
INBOXES = %w[open private].freeze
|
||||
INSTAGRAM_URL_REGEXP = /\A(http(s)?:\/\/)?(?:www.)?instagram.com\/(?=.{1,30}\/?$)([a-zA-Z\d_]\.?)*[a-zA-Z\d_]+\/?\z/.freeze
|
||||
LINKEDIN_URL_REGEXP = /\A(http(s)?:\/\/)?(www.linkedin.com|linkedin.com|[A-Za-z]{2}.linkedin.com)\/.*\z/.freeze
|
||||
MEDIUM_URL_REGEXP = /\A(http(s)?:\/\/)?(www.medium.com|medium.com)\/.*\z/.freeze
|
||||
NAVBARS = %w[default static].freeze
|
||||
STACKOVERFLOW_URL_REGEXP = /\A(http(s)?:\/\/)?(((www|pt|ru|es|ja).)?stackoverflow.com|(www.)?stackexchange.com)\/.*\z/.freeze
|
||||
STREAMING_PLATFORMS = %w[twitch].freeze
|
||||
THEMES = %w[default night_theme pink_theme minimal_light_theme ten_x_hacker_theme].freeze
|
||||
TWITCH_URL_REGEXP = /\A(http(s)?:\/\/)?(www.twitch.tv|twitch.tv)\/.*\z/.freeze
|
||||
USERNAME_REGEXP = /\A[a-zA-Z0-9_]+\z/.freeze
|
||||
MESSAGES = {
|
||||
invalid_config_font: "%<value>s is not a valid font selection",
|
||||
invalid_config_navbar: "%<value>s is not a valid navbar value",
|
||||
invalid_config_theme: "%<value>s is not a valid theme",
|
||||
invalid_editor_version: "%<value>s must be either v1 or v2",
|
||||
reserved_username: "username is reserved"
|
||||
}.freeze
|
||||
|
||||
attr_accessor(
|
||||
:scholar_email, :new_note, :note_for_current_role, :user_status, :pro, :merge_user_id,
|
||||
|
|
@ -8,10 +36,6 @@ class User < ApplicationRecord
|
|||
|
||||
rolify after_add: :index_roles, after_remove: :index_roles
|
||||
|
||||
include AlgoliaSearch
|
||||
include Storext.model
|
||||
include Searchable
|
||||
|
||||
SEARCH_SERIALIZER = Search::UserSerializer
|
||||
SEARCH_CLASS = Search::User
|
||||
DATA_SYNC_CLASS = DataSync::Elasticsearch::User
|
||||
|
|
@ -70,82 +94,48 @@ class User < ApplicationRecord
|
|||
|
||||
devise :omniauthable, :registerable, :database_authenticatable, :confirmable, :rememberable
|
||||
|
||||
validates :email,
|
||||
length: { maximum: 50 },
|
||||
email: true,
|
||||
allow_nil: true
|
||||
validates :behance_url, length: { maximum: 100 }, allow_blank: true, format: BEHANCE_URL_REGEXP
|
||||
validates :bg_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true
|
||||
validates :config_font, inclusion: { in: FONTS, message: MESSAGES[:invalid_config_font] }
|
||||
validates :config_navbar, inclusion: { in: NAVBARS, message: MESSAGES[:invalid_config_navbar] }
|
||||
validates :config_theme, inclusion: { in: THEMES, message: MESSAGES[:invalid_config_theme] }
|
||||
validates :currently_streaming_on, inclusion: { in: STREAMING_PLATFORMS }, allow_nil: true
|
||||
validates :dribbble_url, length: { maximum: 100 }, allow_blank: true, format: DRIBBBLE_URL_REGEXP
|
||||
validates :editor_version, inclusion: { in: EDITORS, message: MESSAGES[:invalid_editor_version] }
|
||||
validates :email, length: { maximum: 50 }, email: true, allow_nil: true
|
||||
validates :email, uniqueness: { allow_nil: true, case_sensitive: false }, if: :email_changed?
|
||||
validates :name, length: { minimum: 1, maximum: 100 }
|
||||
validates :username,
|
||||
presence: true,
|
||||
format: { with: /\A[a-zA-Z0-9_]+\Z/ },
|
||||
length: { in: 2..30 },
|
||||
exclusion: { in: ReservedWords.all, message: "username is reserved" }
|
||||
validates :username, uniqueness: { case_sensitive: false }, if: :username_changed?
|
||||
validates :twitter_username, uniqueness: { allow_nil: true }, if: :twitter_username_changed?
|
||||
validates :github_username, uniqueness: { allow_nil: true }, if: :github_username_changed?
|
||||
validates :employer_name, :employer_url, length: { maximum: 100 }
|
||||
validates :employment_title, :education, :location, length: { maximum: 100 }
|
||||
validates :experience_level, numericality: { less_than_or_equal_to: 10 }, allow_blank: true
|
||||
validates :text_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true
|
||||
validates :bg_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true
|
||||
validates :website_url, :employer_url,
|
||||
url: { allow_blank: true, no_local: true, schemes: %w[https http] }
|
||||
validates :facebook_url,
|
||||
format: /\A(http(s)?:\/\/)?(www.facebook.com|facebook.com)\/.*\Z/,
|
||||
allow_blank: true
|
||||
validates :stackoverflow_url,
|
||||
allow_blank: true,
|
||||
format:
|
||||
/\A(http(s)?:\/\/)?(((www|pt|ru|es|ja).)?stackoverflow.com|(www.)?stackexchange.com)\/.*\Z/
|
||||
validates :behance_url,
|
||||
allow_blank: true,
|
||||
format: /\A(http(s)?:\/\/)?(www.behance.net|behance.net)\/.*\Z/
|
||||
validates :linkedin_url,
|
||||
allow_blank: true,
|
||||
format:
|
||||
/\A(http(s)?:\/\/)?(www.linkedin.com|linkedin.com|[A-Za-z]{2}.linkedin.com)\/.*\Z/
|
||||
validates :dribbble_url,
|
||||
allow_blank: true,
|
||||
format: /\A(http(s)?:\/\/)?(www.dribbble.com|dribbble.com)\/.*\Z/
|
||||
validates :medium_url,
|
||||
allow_blank: true,
|
||||
format: /\A(http(s)?:\/\/)?(www.medium.com|medium.com)\/.*\Z/
|
||||
validates :gitlab_url,
|
||||
allow_blank: true,
|
||||
format: /\A(http(s)?:\/\/)?(www.gitlab.com|gitlab.com)\/.*\Z/
|
||||
validates :instagram_url,
|
||||
allow_blank: true,
|
||||
format: /\A(http(s)?:\/\/)?(?:www.)?instagram.com\/(?=.{1,30}\/?$)([a-zA-Z\d_]\.?)*[a-zA-Z\d_]+\/?\Z/
|
||||
validates :twitch_url,
|
||||
allow_blank: true,
|
||||
format: /\A(http(s)?:\/\/)?(www.twitch.tv|twitch.tv)\/.*\Z/
|
||||
validates :editor_version,
|
||||
inclusion: { in: %w[v1 v2],
|
||||
message: "%<value>s must be either v1 or v2" }
|
||||
validates :facebook_url, length: { maximum: 1000 }, format: FACEBOOK_URL_REGEXP, allow_blank: true
|
||||
validates :feed_referential_link, inclusion: { in: [true, false] }
|
||||
validates :feed_url, length: { maximum: 500 }, allow_nil: true
|
||||
validates :github_username, uniqueness: { allow_nil: true }, if: :github_username_changed?
|
||||
validates :gitlab_url, length: { maximum: 100 }, allow_blank: true, format: GITLAB_URL_REGEXP
|
||||
validates :inbox_guidelines, length: { maximum: 250 }, allow_nil: true
|
||||
validates :inbox_type, inclusion: { in: INBOXES }
|
||||
validates :instagram_url, length: { maximum: 100 }, allow_blank: true, format: INSTAGRAM_URL_REGEXP
|
||||
validates :linkedin_url, length: { maximum: 350 }, allow_blank: true, format: LINKEDIN_URL_REGEXP
|
||||
validates :mastodon_url, length: { maximum: 100 }
|
||||
validates :medium_url, length: { maximum: 200 }, allow_blank: true, format: MEDIUM_URL_REGEXP
|
||||
validates :mostly_work_with, :currently_learning, :currently_hacking_on, :available_for, length: { maximum: 500 }
|
||||
validates :name, length: { in: 1..100 }
|
||||
validates :stackoverflow_url, length: { maximum: 150 }, allow_blank: true, format: STACKOVERFLOW_URL_REGEXP
|
||||
validates :summary, length: { maximum: 1300 }, allow_nil: true
|
||||
validates :text_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true
|
||||
validates :twitch_url, length: { maximum: 100 }, allow_blank: true, format: TWITCH_URL_REGEXP
|
||||
validates :twitter_username, uniqueness: { allow_nil: true }, if: :twitter_username_changed?
|
||||
validates :username, presence: true, exclusion: { in: ReservedWords.all, message: MESSAGES[:invalid_username] }
|
||||
validates :username, length: { in: 2..30 }, format: USERNAME_REGEXP
|
||||
validates :username, uniqueness: { case_sensitive: false }, if: :username_changed?
|
||||
validates :website_url, :employer_url, url: { allow_blank: true, no_local: true }
|
||||
validates :website_url, length: { maximum: 100 }, allow_nil: true
|
||||
|
||||
validates :config_theme,
|
||||
inclusion: { in: %w[default night_theme pink_theme minimal_light_theme ten_x_hacker_theme],
|
||||
message: "%<value>s is not a valid theme" }
|
||||
validates :config_font,
|
||||
inclusion: { in: %w[default sans_serif monospace comic_sans open_dyslexic],
|
||||
message: "%<value>s is not a valid font selection" }
|
||||
validates :config_navbar,
|
||||
inclusion: { in: %w[default static],
|
||||
message: "%<value>s is not a valid navbar value" }
|
||||
validates :website_url, :employer_name, :employer_url,
|
||||
length: { maximum: 100 }
|
||||
validates :employment_title, :education, :location,
|
||||
length: { maximum: 100 }
|
||||
validates :mostly_work_with, :currently_learning,
|
||||
:currently_hacking_on, :available_for,
|
||||
length: { maximum: 500 }
|
||||
validates :inbox_type, inclusion: { in: %w[open private] }
|
||||
validates :currently_streaming_on, inclusion: { in: %w[twitch] }, allow_nil: true
|
||||
validates :feed_referential_link, inclusion: [true, false]
|
||||
validate :conditionally_validate_summary
|
||||
validate :validate_mastodon_url
|
||||
validate :validate_feed_url, if: :feed_url_changed?
|
||||
validate :non_banished_username, :username_changed?
|
||||
validate :unique_including_orgs_and_podcasts, if: :username_changed?
|
||||
validate :conditionally_validate_summary
|
||||
validate :non_banished_username, :username_changed?
|
||||
validate :unique_including_orgs_and_podcasts, if: :username_changed?
|
||||
validate :validate_feed_url, if: :feed_url_changed?
|
||||
validate :validate_mastodon_url
|
||||
|
||||
alias_attribute :positive_reactions_count, :reactions_count
|
||||
alias_attribute :subscribed_to_welcome_notifications?, :welcome_notifications
|
||||
|
|
@ -278,7 +268,6 @@ class User < ApplicationRecord
|
|||
|
||||
# handles both old (prefer_language_*) and new (Array of language codes) formats
|
||||
def preferred_languages_array
|
||||
# return @prefer_languages_array if defined? @preferred_languages_array
|
||||
return @preferred_languages_array if defined?(@preferred_languages_array)
|
||||
|
||||
if language_settings["preferred_languages"].present?
|
||||
|
|
@ -402,7 +391,13 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def unique_including_orgs_and_podcasts
|
||||
errors.add(:username, "is taken.") if Organization.find_by(slug: username) || Podcast.find_by(slug: username) || Page.find_by(slug: username)
|
||||
username_taken = (
|
||||
Organization.exists?(slug: username) ||
|
||||
Podcast.exists?(slug: username) ||
|
||||
Page.exists?(slug: username)
|
||||
)
|
||||
|
||||
errors.add(:username, "is taken.") if username_taken
|
||||
end
|
||||
|
||||
def non_banished_username
|
||||
|
|
@ -547,7 +542,7 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def temp_name_exists?
|
||||
User.find_by(username: temp_username) || Organization.find_by(slug: temp_username)
|
||||
User.exists?(username: temp_username) || Organization.exists?(slug: temp_username)
|
||||
end
|
||||
|
||||
def temp_username
|
||||
|
|
@ -611,8 +606,9 @@ class User < ApplicationRecord
|
|||
|
||||
def validate_feed_url
|
||||
return if feed_url.blank?
|
||||
return if RssReader.new.valid_feed_url?(feed_url)
|
||||
|
||||
errors.add(:feed_url, "is not a valid rss feed") unless RssReader.new.valid_feed_url?(feed_url)
|
||||
errors.add(:feed_url, "is not a valid RSS/Atom feed")
|
||||
end
|
||||
|
||||
def validate_mastodon_url
|
||||
|
|
@ -623,7 +619,7 @@ class User < ApplicationRecord
|
|||
|
||||
errors.add(:mastodon_url, "is not an allowed Mastodon instance")
|
||||
rescue URI::InvalidURIError
|
||||
errors.add(:mastodon_url, "is not a valid url")
|
||||
errors.add(:mastodon_url, "is not a valid URL")
|
||||
end
|
||||
|
||||
def tag_list
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<%= f.label :company_size, "Organization Size" %>
|
||||
<%= f.text_field :company_size, maxlength: 10, placeholder: "Enter a number" %>
|
||||
<%= f.text_field :company_size, maxlength: 7, placeholder: "Enter a number" %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label :story, "Our Story" %>
|
||||
|
|
@ -178,7 +178,7 @@ _italic_ and **bold** is okay. Links, and headers etc. will not show up.
|
|||
</div>
|
||||
<div class="field">
|
||||
<%= f.label :cta_button_url, "Link url" %>
|
||||
<%= f.text_field :cta_button_url, placeholder: "https://example.com" %>
|
||||
<%= f.text_field :cta_button_url, maxlength: 150, placeholder: "https://example.com" %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label></label>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<%= f.label :name %>
|
||||
<%= f.text_field :name %>
|
||||
<%= f.text_field :name, maxlength: 100 %>
|
||||
</div>
|
||||
<%= f.label :profile_image %>
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -3,8 +3,55 @@ require "rails_helper"
|
|||
RSpec.describe Organization, type: :model do
|
||||
let(:organization) { create(:organization) }
|
||||
|
||||
it { is_expected.to have_many(:sponsorships) }
|
||||
it { is_expected.to have_many(:organization_memberships).dependent(:delete_all) }
|
||||
describe "validations" do
|
||||
describe "builtin validations" do
|
||||
subject { organization }
|
||||
|
||||
it { is_expected.to have_many(:api_secrets).through(:users) }
|
||||
it { is_expected.to have_many(:articles) }
|
||||
it { is_expected.to have_many(:classified_listings) }
|
||||
it { is_expected.to have_many(:collections) }
|
||||
it { is_expected.to have_many(:credits) }
|
||||
it { is_expected.to have_many(:display_ads) }
|
||||
it { is_expected.to have_many(:notifications) }
|
||||
it { is_expected.to have_many(:organization_memberships).dependent(:delete_all) }
|
||||
it { is_expected.to have_many(:profile_pins) }
|
||||
it { is_expected.to have_many(:sponsorships) }
|
||||
it { is_expected.to have_many(:unspent_credits).class_name("Credit") }
|
||||
it { is_expected.to have_many(:users).through(:organization_memberships) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_presence_of(:summary) }
|
||||
it { is_expected.to validate_presence_of(:url) }
|
||||
it { is_expected.to validate_presence_of(:profile_image) }
|
||||
it { is_expected.to validate_presence_of(:slug) }
|
||||
it { is_expected.to validate_length_of(:cta_body_markdown).is_at_most(256) }
|
||||
it { is_expected.to validate_length_of(:cta_button_text).is_at_most(20) }
|
||||
it { is_expected.to validate_length_of(:secret).is_equal_to(100) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(50) }
|
||||
it { is_expected.to validate_length_of(:slug).is_at_least(2).is_at_most(18) }
|
||||
it { is_expected.to validate_length_of(:twitter_username).is_at_most(15) }
|
||||
it { is_expected.to validate_length_of(:github_username).is_at_most(50) }
|
||||
it { is_expected.to validate_length_of(:url).is_at_most(200) }
|
||||
it { is_expected.to validate_length_of(:tag_line).is_at_most(60) }
|
||||
it { is_expected.to validate_length_of(:proof).is_at_most(1500) }
|
||||
it { is_expected.to validate_length_of(:location).is_at_most(64) }
|
||||
it { is_expected.to validate_length_of(:email).is_at_most(64) }
|
||||
it { is_expected.to validate_length_of(:company_size).is_at_most(7) }
|
||||
it { is_expected.to validate_length_of(:story).is_at_most(640) }
|
||||
it { is_expected.to validate_length_of(:tech_stack).is_at_most(640) }
|
||||
it { is_expected.to validate_uniqueness_of(:slug).case_insensitive }
|
||||
|
||||
it { is_expected.not_to allow_value("#xyz").for(:bg_color_hex) }
|
||||
it { is_expected.not_to allow_value("#xyz").for(:text_color_hex) }
|
||||
it { is_expected.to allow_value("#aabbcc").for(:bg_color_hex) }
|
||||
it { is_expected.to allow_value("#aabbcc").for(:text_color_hex) }
|
||||
it { is_expected.to allow_value("#abc").for(:bg_color_hex) }
|
||||
it { is_expected.to allow_value("#abc").for(:text_color_hex) }
|
||||
it { is_expected.not_to allow_value("3.0").for(:company_size) }
|
||||
it { is_expected.to allow_value("3").for(:company_size) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#after_commit" do
|
||||
it "on update syncs elasticsearch data" do
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ RSpec.describe User, type: :model do
|
|||
|
||||
describe "validations" do
|
||||
describe "builtin validations" do
|
||||
subject { user }
|
||||
|
||||
it { is_expected.to have_many(:api_secrets).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:articles).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:audit_logs).dependent(:nullify) }
|
||||
|
|
@ -133,13 +135,36 @@ RSpec.describe User, type: :model do
|
|||
it { is_expected.to have_one(:counters).class_name("UserCounter").dependent(:destroy) }
|
||||
it { is_expected.to have_one(:pro_membership).dependent(:destroy) }
|
||||
|
||||
it { is_expected.to validate_uniqueness_of(:username).case_insensitive }
|
||||
it { is_expected.not_to allow_value("#xyz").for(:bg_color_hex) }
|
||||
it { is_expected.not_to allow_value("#xyz").for(:text_color_hex) }
|
||||
it { is_expected.not_to allow_value("AcMe_1%").for(:username) }
|
||||
it { is_expected.to allow_value("#aabbcc").for(:bg_color_hex) }
|
||||
it { is_expected.to allow_value("#aabbcc").for(:text_color_hex) }
|
||||
it { is_expected.to allow_value("#abc").for(:bg_color_hex) }
|
||||
it { is_expected.to allow_value("#abc").for(:text_color_hex) }
|
||||
it { is_expected.to allow_value("AcMe_1").for(:username) }
|
||||
|
||||
it { is_expected.to validate_inclusion_of(:inbox_type).in_array(%w[open private]) }
|
||||
it { is_expected.to validate_length_of(:available_for).is_at_most(500).allow_nil }
|
||||
it { is_expected.to validate_length_of(:behance_url).is_at_most(100).allow_nil }
|
||||
it { is_expected.to validate_length_of(:currently_hacking_on).is_at_most(500).allow_nil }
|
||||
it { is_expected.to validate_length_of(:currently_learning).is_at_most(500).allow_nil }
|
||||
it { is_expected.to validate_length_of(:education).is_at_most(100).allow_nil }
|
||||
it { is_expected.to validate_length_of(:email).is_at_most(50).allow_nil }
|
||||
it { is_expected.to validate_length_of(:employer_name).is_at_most(100).allow_nil }
|
||||
it { is_expected.to validate_length_of(:employer_url).is_at_most(100).allow_nil }
|
||||
it { is_expected.to validate_length_of(:employment_title).is_at_most(100).allow_nil }
|
||||
it { is_expected.to validate_length_of(:inbox_guidelines).is_at_most(250).allow_nil }
|
||||
it { is_expected.to validate_length_of(:location).is_at_most(100).allow_nil }
|
||||
it { is_expected.to validate_length_of(:mostly_work_with).is_at_most(500).allow_nil }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(100).is_at_least(1) }
|
||||
it { is_expected.to validate_length_of(:summary).is_at_most(1300).allow_nil }
|
||||
it { is_expected.to validate_length_of(:username).is_at_most(30).is_at_least(2) }
|
||||
it { is_expected.to validate_uniqueness_of(:github_username).allow_nil }
|
||||
it { is_expected.to validate_uniqueness_of(:twitter_username).allow_nil }
|
||||
it { is_expected.to validate_presence_of(:username) }
|
||||
it { is_expected.to validate_length_of(:username).is_at_most(30).is_at_least(2) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(100).is_at_least(1) }
|
||||
it { is_expected.to validate_inclusion_of(:inbox_type).in_array(%w[open private]) }
|
||||
it { is_expected.to validate_uniqueness_of(:username).case_insensitive }
|
||||
it { is_expected.to validate_url_of(:employer_url) }
|
||||
it { is_expected.to validate_url_of(:website_url) }
|
||||
end
|
||||
|
||||
it "validates username against reserved words" do
|
||||
|
|
@ -828,9 +853,9 @@ RSpec.describe User, type: :model do
|
|||
end
|
||||
|
||||
it "assigns modified username if invalid" do
|
||||
OmniAuth.config.mock_auth[:twitter].info.nickname = "invalid.username"
|
||||
OmniAuth.config.mock_auth[:twitter].info.nickname = "invalid.user"
|
||||
new_user = user_from_authorization_service(:twitter, nil, "navbar_basic")
|
||||
expect(new_user.username).to eq("invalidusername")
|
||||
expect(new_user.username).to eq("invaliduser")
|
||||
end
|
||||
|
||||
it "assigns an identity to user" do
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ require "test_prof/recipes/rspec/before_all"
|
|||
require "test_prof/recipes/rspec/let_it_be"
|
||||
require "test_prof/recipes/rspec/sample"
|
||||
require "sidekiq/testing"
|
||||
# require "validate_url/rspec_matcher"
|
||||
|
||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ require "rails_helper"
|
|||
|
||||
RSpec.describe "UserOrganization", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:organization) { create(:organization, secret: "SECRET") }
|
||||
let(:organization) { create(:organization, secret: SecureRandom.hex(50)) }
|
||||
|
||||
context "when joining an org" do
|
||||
before { sign_in user }
|
||||
|
|
|
|||
14
spec/support/initializers/validate_url.rb
Normal file
14
spec/support/initializers/validate_url.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# NOTE: this is copied from https://github.com/perfectline/validates_url/blob/master/lib/validate_url/rspec_matcher.rb
|
||||
# as the recommended include mechanism doesn't work. Will remove when that's patched correctly
|
||||
RSpec::Matchers.define :validate_url_of do |attribute|
|
||||
match do
|
||||
actual = subject.is_a?(Class) ? subject.new : subject
|
||||
actual.send(:"#{attribute}=", "htp://invalidurl")
|
||||
expect(actual).to be_invalid
|
||||
@expected_message ||= I18n.t("errors.messages.url")
|
||||
expect(actual.errors.messages[attribute.to_sym]).to include(@expected_message)
|
||||
end
|
||||
chain :with_message do |message|
|
||||
@expected_message = message
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue