Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8d9c82762d
commit
ccbe90951f
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class PlansFinder
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params = {})
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
plans = Plan.all
|
||||
by_name(plans)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_name(plans)
|
||||
return plans unless params[:name]
|
||||
|
||||
Plan.find_by(name: params[:name]) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,11 +22,14 @@ module AvatarsHelper
|
|||
end
|
||||
|
||||
def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true)
|
||||
user = User.find_by_any_email(email)
|
||||
if user
|
||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||
return gravatar_icon(email, size, scale) if email.nil?
|
||||
|
||||
if Feature.enabled?(:avatar_cache_for_email, @current_user, type: :development)
|
||||
Gitlab::AvatarCache.by_email(email, size, scale, only_path) do
|
||||
avatar_icon_by_user_email_or_gravatar(email, size, scale, only_path: only_path)
|
||||
end
|
||||
else
|
||||
gravatar_icon(email, size, scale)
|
||||
avatar_icon_by_user_email_or_gravatar(email, size, scale, only_path: only_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,19 +104,23 @@ module AvatarsHelper
|
|||
|
||||
private
|
||||
|
||||
def user_avatar_url_for(only_path: true, **options)
|
||||
return options[:url] if options[:url]
|
||||
|
||||
email = options[:user_email]
|
||||
user = options.key?(:user) ? options[:user] : User.find_by_any_email(email)
|
||||
def avatar_icon_by_user_email_or_gravatar(email, size, scale, only_path:)
|
||||
user = User.find_by_any_email(email)
|
||||
|
||||
if user
|
||||
avatar_icon_for_user(user, options[:size], only_path: only_path)
|
||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||
else
|
||||
gravatar_icon(email, options[:size])
|
||||
gravatar_icon(email, size, scale)
|
||||
end
|
||||
end
|
||||
|
||||
def user_avatar_url_for(only_path: true, **options)
|
||||
return options[:url] if options[:url]
|
||||
return avatar_icon_for_user(options[:user], options[:size], only_path: only_path) if options[:user]
|
||||
|
||||
avatar_icon_for_email(options[:user_email], options[:size], only_path: only_path)
|
||||
end
|
||||
|
||||
def source_icon(source, options = {})
|
||||
avatar_url = source.try(:avatar_url)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ module Avatarable
|
|||
mount_uploader :avatar, AvatarUploader
|
||||
|
||||
after_initialize :add_avatar_to_batch
|
||||
after_commit :clear_avatar_caches
|
||||
end
|
||||
|
||||
module ShadowMethods
|
||||
|
|
@ -127,4 +128,11 @@ module Avatarable
|
|||
def avatar_mounter
|
||||
strong_memoize(:avatar_mounter) { _mounter(:avatar) }
|
||||
end
|
||||
|
||||
def clear_avatar_caches
|
||||
return unless respond_to?(:verified_emails) && verified_emails.any? && avatar_changed?
|
||||
return unless Feature.enabled?(:avatar_cache_for_email, self, type: :development)
|
||||
|
||||
Gitlab::AvatarCache.delete_by_email(*verified_emails)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add API endpoint /application/plan_limits for package file size limits
|
||||
merge_request: 54232
|
||||
author: Jonas Wälter @wwwjon
|
||||
type: added
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: avatar_cache_for_email
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55184
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323185
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
|
|
@ -155,6 +155,7 @@ The following API resources are available outside of project and group contexts
|
|||
| [Namespaces](namespaces.md) | `/namespaces` |
|
||||
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
|
||||
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
|
||||
| [Plan limits](plan_limits.md) | `/application/plan_limits` |
|
||||
| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` |
|
||||
| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
|
||||
| [Project repository storage moves](project_repository_storage_moves.md) **(FREE SELF)** | `/project_repository_storage_moves` |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Access
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Plan limits API **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54232) in GitLab 13.10.
|
||||
|
||||
The plan limits API allows you to maintain the application limits for the existing subscription plans.
|
||||
|
||||
The existing plans depend on the GitLab edition. In the Community Edition, only the plan `default`
|
||||
is available. In the Enterprise Edition, additional plans are available as well.
|
||||
|
||||
NOTE:
|
||||
Administrator access is required to use this API.
|
||||
|
||||
## Get current plan limits
|
||||
|
||||
List the current limits of a plan on the GitLab instance.
|
||||
|
||||
```plaintext
|
||||
GET /application/plan_limits
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------------------------------- | ------- | -------- | ----------- |
|
||||
| `plan_name` | string | no | Name of the plan to get the limits from. Default: `default`. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/application/plan_limits"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"conan_max_file_size": 3221225472,
|
||||
"generic_packages_max_file_size": 5368709120,
|
||||
"maven_max_file_size": 3221225472,
|
||||
"npm_max_file_size": 524288000,
|
||||
"nuget_max_file_size": 524288000,
|
||||
"pypi_max_file_size": 3221225472
|
||||
}
|
||||
```
|
||||
|
||||
## Change plan limits
|
||||
|
||||
Modify the limits of a plan on the GitLab instance.
|
||||
|
||||
```plaintext
|
||||
PUT /application/plan_limits
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------------------------------- | ------- | -------- | ----------- |
|
||||
| `plan_name` | string | yes | Name of the plan to update. |
|
||||
| `conan_max_file_size` | integer | no | Maximum Conan package file size in bytes. |
|
||||
| `generic_packages_max_file_size` | integer | no | Maximum generic package file size in bytes. |
|
||||
| `maven_max_file_size` | integer | no | Maximum Maven package file size in bytes. |
|
||||
| `npm_max_file_size` | integer | no | Maximum NPM package file size in bytes. |
|
||||
| `nuget_max_file_size` | integer | no | Maximum NuGet package file size in bytes. |
|
||||
| `pypi_max_file_size` | integer | no | Maximum PyPI package file size in bytes. |
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/application/plan_limits?plan_name=default&conan_max_file_size=3221225472"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"conan_max_file_size": 3221225472,
|
||||
"generic_packages_max_file_size": 5368709120,
|
||||
"maven_max_file_size": 3221225472,
|
||||
"npm_max_file_size": 524288000,
|
||||
"nuget_max_file_size": 524288000,
|
||||
"pypi_max_file_size": 3221225472
|
||||
}
|
||||
```
|
||||
|
|
@ -400,3 +400,8 @@ listed in the descriptions of the relevant settings.
|
|||
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
|
||||
| `web_ide_clientside_preview_enabled` | boolean | no | Live Preview (allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview). |
|
||||
| `wiki_page_max_content_bytes` | integer | no | Maximum wiki page content size in **bytes**. Default: 52428800 Bytes (50 MB). The minimum value is 1024 bytes. |
|
||||
|
||||
### Package Registry: Package file size limits
|
||||
|
||||
The package file size limits are not part of the Application settings API.
|
||||
Instead, these settings can be accessed using the [Plan limits API](plan_limits.md).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Admin
|
||||
class PlanLimits < ::API::Base
|
||||
before { authenticated_as_admin! }
|
||||
|
||||
feature_category :not_owned
|
||||
|
||||
helpers do
|
||||
def current_plan(name)
|
||||
plan = ::Admin::PlansFinder.new({ name: name }).execute
|
||||
|
||||
not_found!('Plan') unless plan
|
||||
plan
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get current plan limits' do
|
||||
success Entities::PlanLimit
|
||||
end
|
||||
params do
|
||||
optional :plan_name, type: String, values: Plan.all_plans, default: Plan::DEFAULT, desc: 'Name of the plan'
|
||||
end
|
||||
get "application/plan_limits" do
|
||||
params = declared_params(include_missing: false)
|
||||
plan = current_plan(params.delete(:plan_name))
|
||||
|
||||
present plan.actual_limits, with: Entities::PlanLimit
|
||||
end
|
||||
|
||||
desc 'Modify plan limits' do
|
||||
success Entities::PlanLimit
|
||||
end
|
||||
params do
|
||||
requires :plan_name, type: String, values: Plan.all_plans, desc: 'Name of the plan'
|
||||
|
||||
optional :conan_max_file_size, type: Integer, desc: 'Maximum Conan package file size in bytes'
|
||||
optional :generic_packages_max_file_size, type: Integer, desc: 'Maximum generic package file size in bytes'
|
||||
optional :maven_max_file_size, type: Integer, desc: 'Maximum Maven package file size in bytes'
|
||||
optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes'
|
||||
optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes'
|
||||
optional :pypi_max_file_size, type: Integer, desc: 'Maximum PyPI package file size in bytes'
|
||||
end
|
||||
put "application/plan_limits" do
|
||||
params = declared_params(include_missing: false)
|
||||
plan = current_plan(params.delete(:plan_name))
|
||||
|
||||
if plan.actual_limits.update(params)
|
||||
present plan.actual_limits, with: Entities::PlanLimit
|
||||
else
|
||||
render_validation_error!(plan.actual_limits)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -169,6 +169,7 @@ module API
|
|||
mount ::API::AccessRequests
|
||||
mount ::API::Admin::Ci::Variables
|
||||
mount ::API::Admin::InstanceClusters
|
||||
mount ::API::Admin::PlanLimits
|
||||
mount ::API::Admin::Sidekiq
|
||||
mount ::API::Appearance
|
||||
mount ::API::Applications
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
class PlanLimit < Grape::Entity
|
||||
expose :conan_max_file_size
|
||||
expose :generic_packages_max_file_size
|
||||
expose :maven_max_file_size
|
||||
expose :npm_max_file_size
|
||||
expose :nuget_max_file_size
|
||||
expose :pypi_max_file_size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class AvatarCache
|
||||
class << self
|
||||
# Increment this if a breaking change requires
|
||||
# immediate cache expiry of all avatar caches.
|
||||
#
|
||||
# @return [Integer]
|
||||
VERSION = 1
|
||||
|
||||
# @return [Symbol]
|
||||
BASE_KEY = :avatar_cache
|
||||
|
||||
# @return [ActiveSupport::Duration]
|
||||
DEFAULT_EXPIRY = 7.days
|
||||
|
||||
# Look up cached avatar data by email address.
|
||||
# This accepts a block to provide the value to be
|
||||
# cached in the event nothing is found.
|
||||
#
|
||||
# Multiple calls in the same request will be served from the
|
||||
# request store.
|
||||
#
|
||||
# @param email [String]
|
||||
# @param additional_keys [*Object] all must respond to `#to_s`
|
||||
# @param expires_in [ActiveSupport::Duration, Integer]
|
||||
# @yield [email, *additional_keys] yields the supplied params back to the block
|
||||
# @return [String]
|
||||
def by_email(email, *additional_keys, expires_in: DEFAULT_EXPIRY)
|
||||
key = email_key(email)
|
||||
subkey = additional_keys.join(":")
|
||||
|
||||
Gitlab::SafeRequestStore.fetch([key, subkey]) do
|
||||
with do |redis|
|
||||
# Look for existing cache value
|
||||
cached = redis.hget(key, subkey)
|
||||
|
||||
# Return the cached entry if set
|
||||
break cached unless cached.nil?
|
||||
|
||||
# Otherwise, call the block to get the value
|
||||
to_cache = yield(email, *additional_keys).to_s
|
||||
|
||||
# Set it in the cache
|
||||
redis.hset(key, subkey, to_cache)
|
||||
|
||||
# Update the expiry time
|
||||
redis.expire(key, expires_in)
|
||||
|
||||
# Return this new value
|
||||
break to_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Remove one or more emails from the cache
|
||||
#
|
||||
# @param emails [String] one or more emails to delete
|
||||
# @return [Integer] the number of keys deleted
|
||||
def delete_by_email(*emails)
|
||||
return 0 if emails.empty?
|
||||
|
||||
with do |redis|
|
||||
keys = emails.map { |email| email_key(email) }
|
||||
|
||||
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
|
||||
redis.unlink(*keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @param email [String]
|
||||
# @return [String]
|
||||
def email_key(email)
|
||||
"#{BASE_KEY}:v#{VERSION}:#{email}"
|
||||
end
|
||||
|
||||
def with(&blk)
|
||||
Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -85,6 +85,7 @@ RSpec.describe 'Member autocomplete', :js do
|
|||
let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) }
|
||||
|
||||
before do
|
||||
allow(User).to receive(:find_by_any_email).and_call_original
|
||||
allow(User).to receive(:find_by_any_email)
|
||||
.with(noteable.author_email.downcase, confirmed: true).and_return(author)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Admin::PlansFinder do
|
||||
let_it_be(:plan1) { create(:plan, name: 'plan1') }
|
||||
let_it_be(:plan2) { create(:plan, name: 'plan2') }
|
||||
|
||||
describe '#execute' do
|
||||
context 'with no params' do
|
||||
it 'returns all plans' do
|
||||
found = described_class.new.execute
|
||||
|
||||
expect(found).to match_array([plan1, plan2])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing name in params' do
|
||||
before do
|
||||
@params = { title: 'plan2' }
|
||||
end
|
||||
|
||||
it 'returns all plans' do
|
||||
found = described_class.new(@params).execute
|
||||
|
||||
expect(found).to match_array([plan1, plan2])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with existing name in params' do
|
||||
before do
|
||||
@params = { name: 'plan2' }
|
||||
end
|
||||
|
||||
it 'returns the plan' do
|
||||
found = described_class.new(@params).execute
|
||||
|
||||
expect(found).to match(plan2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-existing name in params' do
|
||||
before do
|
||||
@params = { name: 'non-existing-plan' }
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
found = described_class.new(@params).execute
|
||||
|
||||
expect(found).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe AvatarsHelper do
|
||||
include UploadHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
describe '#project_icon & #group_icon' do
|
||||
shared_examples 'resource with a default avatar' do |source_type|
|
||||
|
|
@ -89,33 +89,60 @@ RSpec.describe AvatarsHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#avatar_icon_for_email' do
|
||||
describe '#avatar_icon_for_email', :clean_gitlab_redis_cache do
|
||||
let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) }
|
||||
|
||||
context 'using an email' do
|
||||
context 'when there is a matching user' do
|
||||
it 'returns a relative URL for the avatar' do
|
||||
expect(helper.avatar_icon_for_email(user.email).to_s)
|
||||
.to eq(user.avatar.url)
|
||||
subject { helper.avatar_icon_for_email(user.email).to_s }
|
||||
|
||||
shared_examples "returns avatar for email" do
|
||||
context 'using an email' do
|
||||
context 'when there is a matching user' do
|
||||
it 'returns a relative URL for the avatar' do
|
||||
expect(subject).to eq(user.avatar.url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no user exists for the email' do
|
||||
it 'calls gravatar_icon' do
|
||||
expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
|
||||
|
||||
helper.avatar_icon_for_email('foo@example.com', 20, 2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without an email passed' do
|
||||
it 'calls gravatar_icon' do
|
||||
expect(helper).to receive(:gravatar_icon).with(nil, 20, 2)
|
||||
expect(User).not_to receive(:find_by_any_email)
|
||||
|
||||
helper.avatar_icon_for_email(nil, 20, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no user exists for the email' do
|
||||
it 'calls gravatar_icon' do
|
||||
expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
|
||||
|
||||
helper.avatar_icon_for_email('foo@example.com', 20, 2)
|
||||
end
|
||||
context "when :avatar_cache_for_email flag is enabled" do
|
||||
before do
|
||||
stub_feature_flags(avatar_cache_for_email: true)
|
||||
end
|
||||
|
||||
context 'without an email passed' do
|
||||
it 'calls gravatar_icon' do
|
||||
expect(helper).to receive(:gravatar_icon).with(nil, 20, 2)
|
||||
it_behaves_like "returns avatar for email"
|
||||
|
||||
helper.avatar_icon_for_email(nil, 20, 2)
|
||||
end
|
||||
it "caches the request" do
|
||||
expect(User).to receive(:find_by_any_email).once.and_call_original
|
||||
|
||||
expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url)
|
||||
expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url)
|
||||
end
|
||||
end
|
||||
|
||||
context "when :avatar_cache_for_email flag is disabled" do
|
||||
before do
|
||||
stub_feature_flags(avatar_cache_for_email: false)
|
||||
end
|
||||
|
||||
it_behaves_like "returns avatar for email"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#avatar_icon_for_user' do
|
||||
|
|
@ -346,7 +373,7 @@ RSpec.describe AvatarsHelper do
|
|||
is_expected.to eq tag(
|
||||
:img,
|
||||
alt: "#{options[:user_name]}'s avatar",
|
||||
src: avatar_icon_for_email(options[:user_email], 16),
|
||||
src: helper.avatar_icon_for_email(options[:user_email], 16),
|
||||
data: { container: 'body' },
|
||||
class: "avatar s16 has-tooltip",
|
||||
title: options[:user_name]
|
||||
|
|
@ -379,7 +406,7 @@ RSpec.describe AvatarsHelper do
|
|||
is_expected.to eq tag(
|
||||
:img,
|
||||
alt: "#{user_with_avatar.username}'s avatar",
|
||||
src: avatar_icon_for_email(user_with_avatar.email, 16, only_path: false),
|
||||
src: helper.avatar_icon_for_email(user_with_avatar.email, 16, only_path: false),
|
||||
data: { container: 'body' },
|
||||
class: "avatar s16 has-tooltip",
|
||||
title: user_with_avatar.username
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Entities::PlanLimit do
|
||||
let(:plan_limits) { create(:plan_limits) }
|
||||
|
||||
subject { described_class.new(plan_limits).as_json }
|
||||
|
||||
it 'exposes correct attributes' do
|
||||
expect(subject).to include(
|
||||
:conan_max_file_size,
|
||||
:generic_packages_max_file_size,
|
||||
:maven_max_file_size,
|
||||
:npm_max_file_size,
|
||||
:nuget_max_file_size,
|
||||
:pypi_max_file_size
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not expose id and plan_id' do
|
||||
expect(subject).not_to include(:id, :plan_id)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Gitlab::AvatarCache, :clean_gitlab_redis_cache do
|
||||
def with(&blk)
|
||||
Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def read(key, subkey)
|
||||
with do |redis|
|
||||
redis.hget(key, subkey)
|
||||
end
|
||||
end
|
||||
|
||||
let(:thing) { double("thing", avatar_path: avatar_path) }
|
||||
let(:avatar_path) { "/avatars/my_fancy_avatar.png" }
|
||||
let(:key) { described_class.send(:email_key, "foo@bar.com") }
|
||||
|
||||
let(:perform_fetch) do
|
||||
described_class.by_email("foo@bar.com", 20, 2, true) do
|
||||
thing.avatar_path
|
||||
end
|
||||
end
|
||||
|
||||
describe "#by_email" do
|
||||
it "writes a new value into the cache" do
|
||||
expect(read(key, "20:2:true")).to eq(nil)
|
||||
|
||||
perform_fetch
|
||||
|
||||
expect(read(key, "20:2:true")).to eq(avatar_path)
|
||||
end
|
||||
|
||||
it "finds the cached value and doesn't execute the block" do
|
||||
expect(thing).to receive(:avatar_path).once
|
||||
|
||||
described_class.by_email("foo@bar.com", 20, 2, true) do
|
||||
thing.avatar_path
|
||||
end
|
||||
|
||||
described_class.by_email("foo@bar.com", 20, 2, true) do
|
||||
thing.avatar_path
|
||||
end
|
||||
end
|
||||
|
||||
it "finds the cached value in the request store and doesn't execute the block" do
|
||||
expect(thing).to receive(:avatar_path).once
|
||||
|
||||
Gitlab::WithRequestStore.with_request_store do
|
||||
described_class.by_email("foo@bar.com", 20, 2, true) do
|
||||
thing.avatar_path
|
||||
end
|
||||
|
||||
described_class.by_email("foo@bar.com", 20, 2, true) do
|
||||
thing.avatar_path
|
||||
end
|
||||
|
||||
expect(Gitlab::SafeRequestStore.read([key, "20:2:true"])).to eq(avatar_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#delete_by_email" do
|
||||
subject { described_class.delete_by_email(*emails) }
|
||||
|
||||
before do
|
||||
perform_fetch
|
||||
end
|
||||
|
||||
context "no emails, somehow" do
|
||||
let(:emails) { [] }
|
||||
|
||||
it { is_expected.to eq(0) }
|
||||
end
|
||||
|
||||
context "single email" do
|
||||
let(:emails) { "foo@bar.com" }
|
||||
|
||||
it "removes the email" do
|
||||
expect(read(key, "20:2:true")).to eq(avatar_path)
|
||||
|
||||
expect(subject).to eq(1)
|
||||
|
||||
expect(read(key, "20:2:true")).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "multiple emails" do
|
||||
let(:emails) { ["foo@bar.com", "missing@baz.com"] }
|
||||
|
||||
it "removes the emails it finds" do
|
||||
expect(read(key, "20:2:true")).to eq(avatar_path)
|
||||
|
||||
expect(subject).to eq(1)
|
||||
|
||||
expect(read(key, "20:2:true")).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2499,6 +2499,38 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#clear_avatar_caches" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context "when :avatar_cache_for_email flag is enabled" do
|
||||
before do
|
||||
stub_feature_flags(avatar_cache_for_email: true)
|
||||
end
|
||||
|
||||
it "clears the avatar cache when saving" do
|
||||
allow(user).to receive(:avatar_changed?).and_return(true)
|
||||
|
||||
expect(Gitlab::AvatarCache).to receive(:delete_by_email).with(*user.verified_emails)
|
||||
|
||||
user.update(avatar: fixture_file_upload('spec/fixtures/dk.png'))
|
||||
end
|
||||
end
|
||||
|
||||
context "when :avatar_cache_for_email flag is disabled" do
|
||||
before do
|
||||
stub_feature_flags(avatar_cache_for_email: false)
|
||||
end
|
||||
|
||||
it "doesn't attempt to clear the avatar cache" do
|
||||
allow(user).to receive(:avatar_changed?).and_return(true)
|
||||
|
||||
expect(Gitlab::AvatarCache).not_to receive(:delete_by_email)
|
||||
|
||||
user.update(avatar: fixture_file_upload('spec/fixtures/dk.png'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#accept_pending_invitations!' do
|
||||
let(:user) { create(:user, email: 'user@email.com') }
|
||||
let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
let_it_be(:plan) { create(:plan, name: 'default') }
|
||||
|
||||
describe 'GET /application/plan_limits' do
|
||||
context 'as a non-admin user' do
|
||||
it 'returns 403' do
|
||||
get api('/application/plan_limits', user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as an admin user' do
|
||||
context 'no params' do
|
||||
it 'returns plan limits' do
|
||||
get api('/application/plan_limits', admin)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an Hash
|
||||
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
|
||||
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
|
||||
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
|
||||
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
|
||||
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
|
||||
expect(json_response['pypi_max_file_size']).to eq(Plan.default.actual_limits.pypi_max_file_size)
|
||||
end
|
||||
end
|
||||
|
||||
context 'correct plan name in params' do
|
||||
before do
|
||||
@params = { plan_name: 'default' }
|
||||
end
|
||||
|
||||
it 'returns plan limits' do
|
||||
get api('/application/plan_limits', admin), params: @params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an Hash
|
||||
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
|
||||
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
|
||||
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
|
||||
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
|
||||
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
|
||||
expect(json_response['pypi_max_file_size']).to eq(Plan.default.actual_limits.pypi_max_file_size)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid plan name in params' do
|
||||
before do
|
||||
@params = { plan_name: 'my-plan' }
|
||||
end
|
||||
|
||||
it 'returns validation error' do
|
||||
get api('/application/plan_limits', admin), params: @params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq('plan_name does not have a valid value')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /application/plan_limits' do
|
||||
context 'as a non-admin user' do
|
||||
it 'returns 403' do
|
||||
put api('/application/plan_limits', user), params: { plan_name: 'default' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as an admin user' do
|
||||
context 'correct params' do
|
||||
it 'updates multiple plan limits' do
|
||||
put api('/application/plan_limits', admin), params: {
|
||||
'plan_name': 'default',
|
||||
'conan_max_file_size': 10,
|
||||
'generic_packages_max_file_size': 20,
|
||||
'maven_max_file_size': 30,
|
||||
'npm_max_file_size': 40,
|
||||
'nuget_max_file_size': 50,
|
||||
'pypi_max_file_size': 60
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an Hash
|
||||
expect(json_response['conan_max_file_size']).to eq(10)
|
||||
expect(json_response['generic_packages_max_file_size']).to eq(20)
|
||||
expect(json_response['maven_max_file_size']).to eq(30)
|
||||
expect(json_response['npm_max_file_size']).to eq(40)
|
||||
expect(json_response['nuget_max_file_size']).to eq(50)
|
||||
expect(json_response['pypi_max_file_size']).to eq(60)
|
||||
end
|
||||
|
||||
it 'updates single plan limits' do
|
||||
put api('/application/plan_limits', admin), params: {
|
||||
'plan_name': 'default',
|
||||
'maven_max_file_size': 100
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an Hash
|
||||
expect(json_response['maven_max_file_size']).to eq(100)
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty params' do
|
||||
it 'fails to update plan limits' do
|
||||
put api('/application/plan_limits', admin), params: {}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to match('plan_name is missing')
|
||||
end
|
||||
end
|
||||
|
||||
context 'params with wrong type' do
|
||||
it 'fails to update plan limits' do
|
||||
put api('/application/plan_limits', admin), params: {
|
||||
'plan_name': 'default',
|
||||
'conan_max_file_size': 'a',
|
||||
'generic_packages_max_file_size': 'b',
|
||||
'maven_max_file_size': 'c',
|
||||
'npm_max_file_size': 'd',
|
||||
'nuget_max_file_size': 'e',
|
||||
'pypi_max_file_size': 'f'
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to include(
|
||||
'conan_max_file_size is invalid',
|
||||
'generic_packages_max_file_size is invalid',
|
||||
'maven_max_file_size is invalid',
|
||||
'generic_packages_max_file_size is invalid',
|
||||
'npm_max_file_size is invalid',
|
||||
'nuget_max_file_size is invalid',
|
||||
'pypi_max_file_size is invalid'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'missing plan_name in params' do
|
||||
it 'fails to update plan limits' do
|
||||
put api('/application/plan_limits', admin), params: { 'conan_max_file_size': 0 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to match('plan_name is missing')
|
||||
end
|
||||
end
|
||||
|
||||
context 'additional undeclared params' do
|
||||
before do
|
||||
Plan.default.actual_limits.update!({ 'golang_max_file_size': 1000 })
|
||||
end
|
||||
|
||||
it 'updates only declared plan limits' do
|
||||
put api('/application/plan_limits', admin), params: {
|
||||
'plan_name': 'default',
|
||||
'pypi_max_file_size': 200,
|
||||
'golang_max_file_size': 999
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an Hash
|
||||
expect(json_response['pypi_max_file_size']).to eq(200)
|
||||
expect(json_response['golang_max_file_size']).to be_nil
|
||||
expect(Plan.default.actual_limits.golang_max_file_size).to eq(1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue